Mastering Machine Code on Your ZX81

Mastering Machine Code on Your ZX81

Toni Baker

with illustrations by Cathy Lowe
[ASCIIfied by Thunor]

Reston Publishing Company, Inc.
A Prentice-Hall Company
Reston, Virginia

ISBN: 0-8359-4261-9 (0835942619)

 

Thunor's Foreword

When I was young I would drool over magazine adverts for the ZX80 and ZX81 and eventually persuaded my parents to buy me a ZX81, followed by a 16K RAM pack. I would hog the TV playing with BASIC and wrote a snazzy fruit machine program that took 2 hours to type in (I didn't have a suitable tape deck to save/load programs). What I didn't do was learn Z80 assembly and how to write machine code programs, although I did eventually embrace 8086 assembly which got me my first job in computing. Now that I'm older and have the opportunity to do so, I would like to complete my experience of the ZX81 and write some games in machine code and I found this wonderful book via World of Spectrum (->). Unfortunately it's a PDF where each page is an image of the original, which is acceptable for a desktop computer but I like to read books on portable devices in comfy chairs or in bed, and so I've decided to convert this book to HTML whilst reading and learning from it.

Seeing as this book is a freely downloadable PDF and is very unlikely to experience another print run, I'm not expecting anyone to object although I and everyone else should acknowledge that it is a copyrighted work. What I found particularly poignant when transcribing this material is that Tim Hartnell -- an extremely prolific, bestselling author of books and magazines (->) -- who wrote the foreword, died of cancer in 1991 aged 40.

As far as my transcription goes, I've accomplished the following:

  • Corrected spelling mistakes and Anglicised the English.
  • Proof-read each paragraph or block at transcription time to minimise errors.
  • Removed surplus words and erroneous information using strikethrough.
  • Added missing words, corrections and additional information [within square brackets] with the following exceptions:
    • When adding corrections to program listings (I don't want to introduce characters into code listings) although it is quite clear that my corrected code follows the erroneous code, and in some cases I've added a comment.
    • When the whole document is an addition (such as the page you are currently reading or appendix six) although there will be a comment or something obvious that indicates the document originates with me.
  • Conventionalised the instruction listings throughout the first half of the book by showing the hex-code on the left of the instruction to avoid confusion with operands.
  • Typed-in the programs for the ZX81 (and some for the ZX80) using the z81 emulator (->), fixing bugs where time, scope and willingness allowed and offered the programs for download.
  • Added comments using this format: [Thunor: This is a comment about something important.].
  • Added links to program downloads using this format: [Download available for 16K ZX81 -> chapter16-lenslist.p].
  • Kept the preformatted code width below 80 characters to aid viewing in a reduced width screen or text only browser such as lynx (->).
  • Used ASCII text equivalents of the original illustrations to aid viewing in a text only browser.
  • Added content such as the ZX80 HEXLD3 listing and the complete Draughts listing.
  • Enhanced content such as the more detailed HEXLD3 listings and the Memory Organisation diagram.

I welcome feedback from readers via email. Possibly something needs correcting or maybe you've managed to debug a program that I couldn't; let me know and I'll credit you with the correction at the bottom of the relevant page.

--

Thunor - 9th August 2009

Finished PDF to HTML transcription on 8th October 2009.

 

Some Very Useful Links

ZX81

Sinclair ZX81 BASIC Programming by Steven Vickers i.e. the ZX81 Manual - HTML Conversion by Robin Stuart (->)
Unimproved/Improved ZX81 ROM Differences by Stephen Agate (*DEAD URL*) (->)
The Complete "Improved" ZX81 ROM Assembly File (->)

ZX80

A Course in BASIC Programming by Hugo Davenport i.e. the ZX80 Manual (->)
The Complete ZX80 ROM Assembly File (->)

Z80

Decoding Z80 Opcodes - Of Use to Disassembler and Emulator Writers - by Cristian Dinu (->)

Technical Data

ZX80 and ZX81 Technical Data from the NO$ZX81 Emulator Manual (->)

The ROMs

The GNU GPL'd Open80 and Open81 ROM source code (->)

Emulators

sz81 (*nix, AmigaOS4, portable devices and alternative OS's) (->)
NO$ZX81 (DOS, Win32, works great in DOSBox too) (->)
EightyOne (Win32) (->)

Sinclair Related Forums

forum.tlienhard.com: Das Forum fuer ZX81 und Amiga Freunde (->)
RWAP Services: Sinclair ZX80 / ZX81 Forums (->)
World of Spectrum Forums (->)

 

Foreword

I was staggered when Toni first brought the manuscript for this book to us at the National ZX80 and ZX81 Users' Club. We'd talked about it, and Toni had given me a broad idea of the contents of the book, but until I had the chance to read it, I did not realise just what a comprehensive and easy-to-understand work it would be.

The book has been written for those who know BASIC, but haven't much idea about machine code, and want to get down and master this most useful addition to one's programming skills. We've waited for over a year for a book like this, and now it is here.

If you've decided that GUESS MY NUMBER and SIMON are OK for a while, but now it's time to start exploring the full potential of your computer, and time to begin developing all your potential programming skills, then this book may well prove [to be] just what you've been waiting for.

When Toni first came to us with the idea for the book, I stressed that it must be designed to lead someone who knew absolutely nothing about machine code through from the true basics to the point where they would have a real knowledge of how to use it. I'm pleased to say that she has done just that, and if you work through the book with your ZX81 or ZX80 turned on, entering the programs and routines as instructed, you'll certainly end up Mastering Machine Code on Your ZX81 or ZX80.

--

Tim Hartnell, National ZX80 and ZX81 Users' Club, London, August 1981

 

An Introduction

This book is designed for those people who have a reasonable understanding of BASIC, but whose knowledge of machine code is zero. Starting at first principles with BASIC programs, we gradually introduce the concept of a machine code subroutines, and develop this theory throughout the book. Before long you'll find your understanding of machine language increasing, and you'll soon begin writing your own routines and programs.

Machine language is no more than a second computer language - very much like BASIC is in fact. We start by learning the simplest of instructions, and become familiar with them by using them in BASIC programs. An example would be a SCROLL program given in chapter four, which moves the screen downward instead of upward. This effect is rather interesting, and certainly surprising.

Printing strings is the next thing covered, and this involves making use of the PRINT subroutine in the ROM. The routine is demonstrated by printing a draughts board which later on in the book we shall make use of.

We explain the machine code equivalent of the INKEY$ function, and use the technique of scanning the keyboard to write a typewriter-type program which uses greatly enlarged versions of the keyboard characters.

The same keyboard scanning technique is used to make musical notes in [a] rather surprising manner. Two whole octaves can be produced from your machine, enabling you to play a wide variety of tunes at the touch of the keyboard.

The computer is made to generate many intricate and fascinating displays in the program LIFE. It challenges the skill of an unwary human operator in graphics games such as SPIRAL. A draughts program is included, with several interesting features. This is actually a teaching game because you are encouraged to add your own features to it as you progress.

Careful study of the listings of these programs will teach you a great deal about machine code, but of course the biggest steps in learning will come from experiment. By writing your own programs, or by adapting mine - by all means do - they are intended for this purpose, and some in fact are deliberately improvable for this reason.

To make the best use of this book you are advised to work through from start to finish, and where asked to alter or improve programs you should make an attempt to do so. It's not difficult, since the book progresses very slowly, but will require some thought.

The last two chapters in the book are rather ambitious. An algorithm by which the ROM may be disassembled is given, but only guidelines are given as to how you may write a program for it. All of the arithmetic subroutines are explained in detail, even NEW ROM floating point functions like SIN and COS, and how the numbers are stored.

The heavily tabulated appendices at the back are designed to be used as a source of reference throughout the book. Any piece of information you need to know can generally be found in these appendices, or in chapter eight, which is a kind of "catalogue" of machine code.

The first chapter begins on the next page, and starts with an introduction to the use of "hexadecimal"....

 

 

Introduction to Hexadecimal and Machine Code

OK, so your ZX80/81 is all fired up and ready, and that ominous inverse-K is sitting there glaring at you from its little corner and waiting for you to type something in. What do you do?

Well the first thing is to set up the machine so that it can accept programs in machine code instead of in BASIC. This is not difficult, but unfortunately for us, when Sinclair designed his machine he forgot to include a button saying GO-INTO-MACHINE-CODE-MODE, so the routine for doing this is going to have to be a BASIC program.

If you have a NEW ROM machine type one of the following sequences, depending on how much memory you have:

1K                4K               16K  POKE 16388,173    POKE 16388,32    POKE 16388,48  POKE 16389,67     POKE 16389,78    POKE 16389,117  NEW               NEW              NEW  

[Download available for 16K ZX81 -> chapter02-setramtop.p. Well you don't need me to supply you with BASIC 3 line programs, but I do know that from here on in you'll be asked to set RAMTOP to various decimal or hexadecimal addresses and so this heavily modified version of the author's listing will save you a lot of typing (I'm assuming you're using an emulator and are NOT going to load this from tape). I recommend you rename it to something like "s.p" so that you can LOAD "S", select decimal or hex, enter an address and then LOAD "your-program".]

The effect of this is quite straightforward. The addresses 16388 and 16389 together hold a system variable called RAMTOP. It contains the address of the first byte which the computer cannot use - at least not for BASIC. Under normal circumstances this address is the one immediately after the last byte in memory, so that the whole of the memory is available for BASIC programming. What we have done is to alter that address, so that some of the memory is unavailable for BASIC, and becomes a safe place in which to store machine code.

If you have an OLD ROM machine, don't worry - you can still store machine code in spare areas of the memory, but you MUST NOT type NEW, or you will lose it all.

The best addresses in which to store machine code are best found by trial and error. We shall adopt the following standard addresses, which should work perfectly for all of the routines in this book:

OLD ROM  1K:  17225  NEW ROM  1K:  17325           4K:  20000          16K:  30000  

Throughout the remainder of this book I shall use the address 30000. Please read this as one of the alternatives above if you have less than 16K.

OK:- Now we're ready to start. Type in the following BASIC program:

                    10 LET X=30000  When you have       20 LET A$=""  typed this program  30 IF A$="" THEN INPUT A$  in name it          40 IF A$="S" THEN STOP  "HEXLD" and don't   50 POKE X,16*CODE A$+CODE A$(2)-476  forget to SAVE      60 LET X=X+1  it.                 70 LET A$=A$(3 TO )                      80 GOTO 30  

(For the OLD ROM you must replace lines 50 and 70 as follows:)

50 POKE X,16*CODE (A$)+CODE (TL$(A$))+36  70 LET A$=TL$(TL$(A$))  

[Download available for 16K ZX81 -> chapter02-hexld.p]

Can you see how the program works? Or at least what it does? In brief - it will accept a machine code program, and will store it at addresses 30000 onwards. (Or 20000, or whatever.) The program will stop when you input an "S". Note that although it will enter machine code, it will NOT attempt to run it.

Now for the big question you've all been dying to ask - what exactly IS machine code? Well machine code, or machine language as it's otherwise known, is another computer language - much like BASIC is - only at a much lower level, which means that very complicated instructions, such as FOR/NEXT loops, are simply not available. However this also makes it quite an easy language to learn. Like BASIC it consists of a set of instructions, each of which tells the computer to do a different, and quite specific, task. One such instruction is RET, which is more or less equivalent to BASIC's RETURN.

Unlike BASIC, however, the computer isn't programmed to understand all of the various instructions as we do. If you were to RUN the above program and enter "RET" then this simply would not make sense to the poor old ZX81 (or '80). To make life easier for it, every instruction has a numerical code, which it DOES understand directly. For example the code for RET is 201. Every code lies somewhere in the range 0-255, and it is usually more convenient to write these codes in a system called HEXADECIMAL.

COUNTING IN HEXADECIMAL

Our friend Mr. Sinclair briefly covers this obscure system of counting in the ZX81 instruction manual by describing an imaginary race of sixteen fingered "Martians" who would regard counting in tens as being equally absurd. In these modern days of science we know enough about Mars to realise that it is extremely unlikely to host sixteen fingered people, but the principle of counting in sixteens is still very sound.

Briefly, for those who have not read the ZX81 manual, hexadecimal, or hex for short, is a means of counting which uses sixteen symbols instead of ten. The first ten sysmbols are the same as the ones we're used to. These are:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9.

There are six new symbols which represent the numbers 10 to 15. These are:

A, B, C, D, E, F.

The fun really starts when we want to represent numbers bigger than fifteen, for believe it or not, sixteen is written as 10! Worse still, seventeen is written 11. This continues up as far as twenty-five, written 19, and then when we come to twenty-six we have to start using the new symbols again; twenty-six becomes 1A.

A complete table of all of the numbers from 0 to 255 is shown here. This is intended to help you understand the hexadecimal system of counting. You should try to refer to it as little as possible, but don't worry if you find yourself using it all the time at first, you'll find you get used to it much quicker than you expect.

The symbols down the left hand side are the first hex digit, the symbols along the top are the second digit. The leading zeros may of course be ommitted if there are any, but it is sometimes more convenient to leave hex codes as two digits rather than one.

If there is ever any confusion about whether a number is written in hex or not, you should make it clear by writing a small letter h (standing for hex) or a small letter d (for decimal) after the number, so that 19h means twenty-five, and 19d means nineteen. Usually you won't need to do this because numbers like CD can only possibly be hexadecimal, and number like 118, which are three digits long, can only be decimal. (Computing does not use hex numbers which are three digits long, though it does uses ones which are FOUR digits long).

Knowing at least the fundamentals of counting in hex is virtually paramount as far as machine code is concerned, so don't be afraid to keep coming back to this section, or to keep refering to the table - that's what it's there for.

+---------------------------------------------------------------------+  |       0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F |  |   +-----------------------------------------------------------------+  | 0 |   0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15 |  | 1 |  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31 |  | 2 |  32  33  34  35  36  37  38  39  40  41  42  43  44  45  46  47 |  | 3 |  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63 |  | 4 |  64  65  66  67  68  69  70  71  72  73  74  75  76  77  78  79 |  | 5 |  80  81  82  83  84  85  86  87  88  89  90  91  92  93  94  95 |  | 6 |  96  97  98  99 100 101 102 103 104 105 106 107 108 109 110 111 |  | 7 | 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |  | 8 | 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |  | 9 | 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |  | A | 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |  | B | 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |  | C | 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |  | D | 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |  | E | 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |  | F | 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |  +---+-----------------------------------------------------------------+  

There are fundamental differences between machine code programming and BASIC programming. One of the most fundamental differences is that of LINE NUMBERS.

As you know, every BASIC instruction in a program must be preceded by a line number, so that the computer knows in which order to execute them. If no line number is given the computer will interpret the instruction as a COMMAND and will execute it immediately.

In machine code, there are no line numbers. Also, the ZX80/81 will not allow you to use machine code instructions as commands, they MUST form part of a program. The instructions are executed in the order that they are stored. For example, if the computer had just finished executing the instruction which was stored in location 30000, it would then go on to execute the instruction held in location 30001. It will continue in this way until it recieved an instruction telling it to do otherwise.

Unlike BASIC, it will NOT automatically stop when it reaches the end of the program. It will plough right on through the addresses, and every time it finds a number which is not zero it will simply treat that number as a code for some instruction and try to execute it. Usually this will result in what is called a CRASH.

ABOUT CRASHING

Crashing is the name we give to what happens when your (up until now at least moderately well-behaved) Sinclair machine unwittingly tries to execute something it shouldn't, or if there is a drastic mistake in your machine-coding which will confuse the poor machine and give it a rather nasty headache. The effect of a crash is very unmistakable - The screen will either go blank or will go in to its "LET'S-PRODUCE-SOME-MODERN-ART" mode. If this happens you will get pretty (or otherwise) patterns on your TV not unlike those produced during SAVE.

When this happens you will undoubtedly try to break out, by using the BREAK key, and discover to your horror that the BREAK key doesn't work! In fact NONE of the keys will work after a crash, except possibly to produce slight variations in the TV picture. This is the prime reason why we dislike crashes, for THE ONLY WAY TO THEN GET BACK TO NORMAL IS TO DISCONNECT THE POWER SUPPLY! When you reconnect you will of course have lost all of your program and will have to reLOAD it.

If a BASIC program contains a mistake it will usually NOT WORK.

If a machine-code program contains a mistake it will usually CRASH!

HOW TO PREVENT CRASHES

We have already stated that a machine code program will not automatically stop at the end of a program - it must be told to do so by a specific instruction. For the ZX80/81, that instruction is RET. (Return - i.e. return to BASIC).

There is an instruction similar to STOP in BASIC, that instruction is HALT. DO NOT USE THIS INSTRUCTION! On other computers you can use HALT to end a program, but not on the ZXs. HALT produces a condition similar to a crash, for it means "Do nothing whatsoever until somebody breaks out." The problem is of course that you CAN'T break out because you'll find that the keyboard no longer works. To summarise: To end a machine code program ALWAYS use RET. NEVER use HALT.

A program must have at least one return instruction in it somewhere, otherwise it will never return to BASIC, unless you actually disconnect the power supply, and this is not usually a desirable thing to do.

This chapter has dealt with how to reserve space for machine code programs, and has given you a program with which to load it. It has not told you how to make use of this program, nor has it explained how to run machine code programs once they have been loaded. The fundamentals of counting in hex have been introduced, and the notion of a crash has been mentioned.

Once you have understood this chapter, you may turn to chapter three for your first lesson in machine language programming.

 

 

 

Simple Arithmetic

"HEXLD" REVISITED

You remember the program I asked you to save in chapter two? Well now it's time to break it out, wipe the dust from it, and after you've reserved yourself some machine code space as described at the start of the previous chapter, you can LOAD it.

Now press RUN and newline.

The program is waiting for a string input. What it in fact wants is some kind of HEXADECIMAL input. This means that every time you want to input a machine language instruction you have to know its numerical code, and you have to know it in hex.

The code for RET is, as we have already stated, 201. What is this in hexadecimal? Divide it by sixteen and you get twelve remainder nine. Now the hex symbol for twelve is C, the hex symbol for nine is 9. If you look 201 up in the table in chapter two you'll find that it is written C9. Is this a coincidence?

Input C9. You have now told the computer that the first instruction of your machine language program is RET.

The computer is now waiting for another input. Break out of the program by inputting "S".

Your program is now complete. It consists of the single instruction RET. This is usually written

C9        RET  

to remind you that the hex-code for RET is C9. The machine language instructions are sometimes called OPCODES to distinguish them from their corresponding HEX-CODES, C9 is a hex-code, RET is an opcode. Hex-codes are used by the machine - it will not understand opcodes. Conversely, opcodes are used by humans, because we would find it extremely difficult to work in hex-codes.

If you now look at the screen you'll see that the computer has gone back to command mode. It is waiting for an instruction. Suppose we now wish to run the machine code program that we've just typed in. We can do this either as part of a BASIC program, or, as we are going to do, as a direct command. If your routine was loaded to address 30000 then the command is

PRINT USR 30000  

If your routine began at some other address simply use this figure instead of the 30000 in the above command. Note that OLD ROM users will need brackets around the number following the word USR.

You will have found that the computer has printed 30000 in the top left hand corner of the screen. Can you see why this is so? It started off with the number 30000 - this is the address you gave it when you typed PRINT USR 30000. The program told it to RET, or return to BASIC, having done nothing at all to this number, so that's exactly what it did - it returned to BASIC and it returned the number 30000 with it.

Before we can advance to learning any more instructions, we are going to have to break for a while and explore the concept of REGISTERS. A register is like a variable, in that it has a name - usually a letter of the alphabet - and it can store numbers in much the same way that BASIC variables can. The big difference is that registers can only store numbers in the range 0 to 255. (Or in hex, 00 to FF).

There are seven registers which are most commonly used for machine code routines. Their names are A, B, C, D, E, H, L. To give a larger degree of flexibility it is also possible to use the registers in pairs. When this is done you can alternatively store numbers either in the range -32767 to 32767 or in the range 0 to 65535, using the register-pairs, as they are known, BC, DE, and HL.

To make this clear, if register H contains the value 2, and register L contains the value 23, the register-pair HL is said to contain the value 2*256+23, which is 535. If H were to contain a value of 128 or more, then HL could instead be thought of as containing a negative value, equal to (H-256)*256 L.

THE INSTRUCTION LD

Consider the BASIC instruction LET A=42. In machine language we assign variables (registers) using the instruction LD. We could for example write LD A, 42. Note there is no equals symbol as there is in BASIC, instead a comma (,) is used to seperate the A from the number. The effect of this instruction is exactly what you'd expect it to be - the previous value of A is overwritten, and a new value, in this case 42, is assigned in its place.

Each different LD instruction has a different code. For example the code for LD A, is 3E. The number 42 is 2A in hex, so the full instruction in hex is 3E2A. Note that this is TWO BYTES in length (every two hex digits is one byte). Compare this with the number of bytes in the BASIC instruction LET A=42.

The remaining codes are as follows:

3E        LD A,  06        LD B,  01        LD BC,  0E        LD C,  16        LD D,  11        LD DE,  1E        LD E,  26        LD H,  21        LD HL,  2E        LD L,  

Using the program "HEXLD", enter the following program, by inputting the symbols in the left hand column. Once the whole program has been entered, break out by inputting "S".

2600      LD H,00h  2E2A      LD L,2Ah  C9        RET  

Now that the program is loaded you can run it by typing as a direct command PRINT USR 30000. What happens?

Now try entering this program:

0600      LD B,00  0E2A      LD C,2A  C9        RET  

If you possess an OLD ROM then the first program should return a value of forty-two, and the second program should return a value of 30000. However the NEW ROM will work the other way round, and return 30000 for the first program, and forty-two for the second. The reason is the fact that USR works differently for the two ROMs. For the OLD ROM, USR something means load HL with that something and then run the machine code. On the NEW ROM it means load BC with that something before running the machine code. When BASIC returns the number you are left with is the value HL (OLD ROM) or BC (NEW ROM). The first program leaves BC unchanged (on the NEW ROM it will have been assigned 30000) but will load HL with 42. The OLD ROM will return HL (42) and the NEW ROM will return BC (30000). The second program is the reverse. It will leave HL unchanged. (On the OLD ROM HL will have been assigned 30000) BC will then be loaded with 42. Which ROM will return which number? Which ROM do you have? Try it and see.

HL, by the way, stands for High/Low. Because any number in HL is stored in two parts the part that is stored in H is called the HIGH part, and the part that is stored in L is called the LOW part. BC and DE also have high and low parts, with the first letter for the high part, and the second letter for the low part.

What is 42 in hexadecimal to FOUR digits? Answer:- 002A. What do you think the following program will do? Try it and find out.

OLD ROM    NEW ROM  21002A     01002A  C9         C9  

You may be surprised to discover that when you type PRINT USR 30000 to run it you get the answer 10752 - NOT 42! Now run this program:

OLD ROM    NEW ROM  212A00     012A00  C9         C9  

Now you will get 42. Notice the way the 2A and the 00 have been swapped around. Although this is rather strange it is in fact USUAL for the ZX80/81 to think of its numbers as having the low part FIRST, and the high part SECOND. In fact with the exception of line numbers, and in FOR/NEXT loops the ZX80/81 will always store its numbers "the wrong way around." In the instruction LD HL, the first byte is always 21h. The second byte is the new value of L, and the last byte is the new value of H. Not that this is always three bytes long.

To summarise: The LD instructions which operate on register pairs, rather than single registers, use values stored "the wrong way round."

LDing From One Variable To Another

If we were restricted in BASIC to only using LET instructions of the form LET A= a number we would be a bit stuck. We need to be a bit more flexible that that. For instance something like LET A=B would be useful. Well we can certainly manage that in machine code. The codes are:

+-----+-----------------------------+  | LD  |  A   B   C   D   E   H   L  |  +-----+-----------------------------+  | A   |  7F  78  79  7A  7B  7C  7D |  | B   |  47  40  41  42  43  44  45 |  | C   |  4F  48  49  4A  4B  4C  4D |  | D   |  57  50  51  52  53  54  55 |  | E   |  5F  58  59  5A  5B  5C  5D |  | H   |  67  60  61  62  63  64  65 |  | L   |  6F  68  69  6A  6B  6C  6D |  +-----+-----------------------------+  

In the above table you read the left-hand-column registers first, and the top-row registers second, so that the code for LD D,A is 57, and the code for LD A,D is 7A. Notice how each of these is a mere ONE BYTE in length. Compare this with the equivalent BASIC instruction LET A=D, which takes a total of ten bytes in all (eight on the OLD ROM) if you include the line number, the line length, and the end of line character.

And now for some arithmetic. Those of you who have been thinking ahead may have been wondering how we can add and subtract registers like we can in BASIC. After all, the single-byte representation of LD A,B for example, doesn't leave a lot of room for manoeuvre.

In fact, we use a different instruction altogether to add registers together. The instruction is ADD. You can think of an ADD instruction as being a LET statement with an expression involving "plus" on the right hand side of the equals. A useful example would be

                      ADD HL,DE  which has the effect  LET HL=HL+DE  

The instruction ADD HL,DE will take the contents of the register-pair DE, and will add this number to the contents of register-pair HL. The result of this calculation will then be stored in register-pair HL. As you can see, if we were working in BASIC and we were dealing in variables instead of register-pairs then we would have performed the operation LET HL=HL+DE.

Well almost, but not quite. There is in fact one small difference - the difference is what happens when you get what is called an overflow. You see register pairs can store all of the (hexadecimal) numbers between 0000 and FFFF. Those from 0000 to 7FFF are the integers 0 to 32767 in decimal, those from 8000 to FFFF can either represent numbers from 32768 to 65535, or numbers in the range -32768 to -1. You can use either form, but when the USR function returns a decimal number to BASIC the OLD ROM will use -32768 to 32767 and the NEW ROM will return a number between 0 and 65535. An OVERFLOW is what happens when you go beyond these ranges. In BASIC any overflow will simply stop the program and give you an error message. What do you suppose will happen in machine code?

OLD ROM first then: the BASIC for the OLD ROM deals with the numbers from -32768 to 32767. What is the number 32767 in hexadecimal? Dividing by 256 to split it into two bytes gives 127 remainder 255, so the first byte is 127 (7F) and the second byte is 255 (FF). Now enter the program:

OLD ROM ONLY  110100    LD DE,1  21FF7F    LD HL,32767  19        ADD HL,DE  C9        RET  

The program will simply try to add one to the number 32767. Run it (using the direct command PRINT USR(30000)) and the result may astonish you. By the way, did you notice how the 00 and 01, and also the 7F and FF, had been swapped around in the above listing? You must always remember to do this in machine code. Did you notice also that the code for adding the registers (ADD HL,DE) was only one byte long? In fact the byte 19h. All of the ADD codes are one byte in length.

If you want to add one to BC for instance then you must do something like this

210100    LD HL,1  09        ADD HL,BC  44        LD B,H  4D        LD C,L  

Notice how B and C have to be loaded seperately since there is no such instruction as LD BC,HL. If you have a NEW ROM and you want to see what happens on an overflow load and run this program:

NEW ROM ONLY  210100    LD HL,1  01FFFF    LD BC,65535  09        ADD HL,BC  44        LD B,H  4D        LD C,L  C9        RET  

Another thing you should notice is that only register-pairs may be added to register pairs, and that only single-registers may be added to single-registers. You may NOT add a single-register to a register pair, or vice versa. ADD A,HL is WRONG.

09        ADD HL,BC  19        ADD HL,DE  29        ADD HL,HL  87        ADD A,A  80        ADD A,B  81        ADD A,C  82        ADD A,D  83        ADD A,E  84        ADD A,H  85        ADD A,L  

If overflowing register-PAIRS had you thinking, then think about overflowing SINGLE registers, for they can only hold numbers from 0 to 255. What happens when they overflow? Well yes, they simply start again at zero, but the question is can we do anything about this? In fact we can. Whenever we add two numbers, sometimes there is an overflow, or CARRY, and sometimes there isn't. The computer sets aside a NEW register, called F (which we cannot use directly) to store various bits of information. One of these bits of information is called the CARRY BIT.

An ADD instruction will always reassign the CARRY BIT. If there is no carry, it will be set to zero. If there is a carry, it will be set to one. We can use the value of the CARRY BIT by using the machine code instruction ADC, which means "ADD with CARRY".

It works like this. Suppose the machine comes across the instruction ADC A,B. It will take the contents of register B, and it will add the contents of register A, as in the previous instruction ADD A,B, and then it will add the CARRY BIT to this new number. Having done this it will store the result in register A, overflowing if necessary. The carry bit will always be reassigned to either zero or one, depending on whether or not there is an overflow.

So       ADD A,B  effectively means  LET A=A+B                    followed by        LET CARRY=INT((A+B)/256)    whereas  ADC A,B  effectively means  LET A=A+B+CARRY                    followed by        LET CARRY=INT((A+B+CARRY)/256)  

Study the programs that follow. If the value of the A register is irrelevant, then are these programs equivalent (i.e. do they both do the same thing?) or not? Can you understand why?

The first program is

118533    LD DE,13189  21C77B    LD HL,31687  19        ADD HL,DE  44        LD B,H       <- NEW ROM ONLY  4D        LD C,L       <- NEW ROM ONLY  C9        RET  

and the second program is

1633      LD D,51  1E85      LD E,133  267B      LD H,123  2EC7      LD L,199  7D        LD A,L  83        ADD A,E  6F        LD L,A  7C        LD A,H  8A        ADC A,D  67        LD H,A  44        LD B,H       <- NEW ROM ONLY  4D        LD C,L       <- NEW ROM ONLY  C9        RET  

In fact they are exactly the same. You can learn two things from this; firstly that the instruction LD does not in any way affect or alter the value of CARRY, for if it did the two LD instructions between ADD A,E and ADC A,D would really mess things up; secondly that the instruction ADD HL,DE is much shorter, and much neater, than going all round the houses by adding each byte seperately. And never forget to swap the order of the bytes round in LD instructions on pairs - compare the first two lines of program one with the first four lines of program two.

Now run both of the above programs just to verify that they are the same. What would happen if the ADC A,D in program two were replaced by ADD A,D?

Now that you understand the difference between ADD and ADC we shall go on to cover some other ways of adding. First though, the codes for ADC:

ED4A      ADC HL,BC  ED5A      ADC HL,DE  ED6A      ADC HL,HL  8F        ADC A,A  88        ADC A,B  89        ADC A,C  8A        ADC A,D  8B        ADC A,E  8C        ADC A,H  8D        ADC A,L  

Notice how the codes for ADC HL, are all TWO bytes long, rather than one. The first byte is ED, and the second byte depends on what you are adding. Do not think of ED as meaning ADC HL, though, since it may have many other possible meanings as well, depending on what follows it.

ADDING CONSTANTS

We can also use the ADD and ADC instructions to add numerical constants directly to the A register. An example would be ADD A,3 which would, as you'd expect, add three to the current value of A. It would also assign CARRY to one or zero, depending on whether or not this addition caused A to overflow beyond 255.

The code for ADD A, is C6, and the code for ADC A, is CE. Note that we cannot add constants to any register other than A.

Suppose we wished to add 57 to HL. One way would be as follows:

113900    LD DE,57d  19        ADD HL,DE  

but this method has the disadvantage that it requires the use of DE, which may be needed for other things. Another way of achieving the same thing, but this time only bringing the A register into use, is thus:

7D        LD A,L  C639      ADD A,57d  6F        LD L,A  7C        LD A,H  CE00      ADC A,0  67        LD H,A  

Notice how the instruction ADC A,0 was used to add any carry digit there may have been from adding 57 to L.

AND FINALLY....

There is one more way that we can add constants to a register, and that is by using the instruction INC.

INC A means add one to the value of A. Unlike ADD, INC may be used on ANY register, so statements like INC D (add one to the value of D) or INC DE (add one to the value of register-pair DE) are allowed.

If A contained the value 255, then INC A will set A to zero, but WITHOUT setting CARRY equal to one. In fact INC will not alter the value of CARRY at all. If it was one before an INC instruction, it will be one after such an instruction. If it was zero before an INC, it will be zero after an INC.

In short:

INC B  is equivalent to  LET B=B+1    03        INC BC  13        INC DE  23        INC HL  3C        INC A  04        INC B  0C        INC C  14        INC D  1C        INC E  24        INC H  2C        INC L  

Remember, the difference between ADD A,1 and INC A is that ADD A,1 will assign a new value to CARRY, whereas INC A will leave it unaltered. INC, by the way, is short for INCREMENT.

The value of CARRY can be altered directly without any of the other registers being affected. There is an instruction SCF, which stands for SET CARRY FLAG, and its job is to assign to CARRY a value of one. The code for this instruction is 37h. Alternatively, it is possible to reset CARRY to zero, although there is no specific instruction to do this. One way would be to say ADD A,0 for example. Adding zero will of course leave the value of A unchanged, but an ADD instruction will always reassign CARRY.

CARRY is called a FLAG rather that a register, because it can only store the numbers one and zero. It is not possible to assign a value of two to CARRY, nor any other number in fact, only one and zero.

There is one other way to directly change the value of the carry flag, that is by using the instruction CCF, which stands for COMPLEMENT CARRY FLAG. It will change the value of CARRY from one to zero, or from zero to one. In BASIC terms these instructions may be listed thus:

37        SCF        LET CARRY=1  C600      ADD A,0    LET CARRY=0  3F        CCF        LET CARRY=1-CARRY  

SUBTRACTION

In machine language, there are codes for subtraction, which are used in exactly the same way as the addition codes. The instruction is SUB, for SUBTRACT, and in exactly the same way as ADD, there is also an instruction SBC, for SUBTRACT WITH CARRY.

It works like this. SUB A,B will take the value of register B, and will subtract it from the value of register A. The result of this calculation is stored in register A. The carry flag is reassigned to zero if there is no overflow, or to one if the result overflows to below zero (in which case the value of A will have 256 added to it).

SUB A,B may also be written as simply SUB B, because it is only the A register which may have things subtracted from it. Do not get confused by this convention - the two terms mean exactly the same thing.

The codes for SUB are:

97        SUB A,A  90        SUB A,B  91        SUB A,C  92        SUB A,D  93        SUB A,E  94        SUB A,H  95        SUB A,L  

It is also possible to subtract numerical constants from the A register. For example the instruction SUB A,100 will subtract 100 from the number stored in register A. The result is stored in register A, and the carry flag is reassigned to zero if there is no overflow, or to one if there is an overflow. The code for subtracting constants is D6, so that SUB A,100 is D664 (since 100 is written as 64 in hexadecimal)

D6        SUB A,  

You should note the fact that although there are instructions such as ADD HL,BC, there are NO instructions to subtract register-pairs.

SUBTRACT WITH CARRY (SBC) on the other hand, WILL work for register pairs, but as with ADD and ADC, only the value of HL may be altered. For single registers it is only the value of A that may be changed.

SBC A,C will subtract the value of C from the value of A, and will then subtract the value of CARRY from this result. The final answer will be stored in register A. CARRY will be reassigned as before.

The codes for SBC are:

ED42      SBC HL,BC  ED52      SBC HL,DE  ED62      SBC HL,HL  9F        SBC A,A  98        SBC A,B  99        SBC A,C  9A        SBC A,D  9B        SBC A,E  9C        SBC A,H  9D        SBC A,L  

To SUBTRACT WITH CARRY a numerical constant from the A register the code is DE followed by the number itself in hex. What is the code for SBC A,200? What precisely does this instruction do?

DEC is short for DECREMENT. It is, as you may have gathered from its weird sounding name, the opposite of INC (increment). Its purpose is to decrease the value of any register by one without changing the value of the carry flag. So DEC DE has the effect of LET DE=DE-1, remembering of course that if you decrement zero you get 255.

Compare these two routines:

C600      ADD A,0  D602      SUB A,2  ED52      SBC HL,DE    and    C600      ADD A,0  3D        DEC A  3D        DEC A  ED52      SBC HL,DE  

Are they the same? And if not, why not? One of these two routines will subtract two from A, and will subtract DE from HL - the other routine is wrong. Which is which?

In fact it is the first example which is wrong. The instruction SBC HL,DE will subtract both DE and the carry flag, so the carry flag must first be reset to zero. This is what ADD A,0 is for. But having done that, the first example will alter the carry flag AGAIN with the instruction SUB A,2. The chances are that it will be reset to zero, but if A happens to equal one or zero then the SUB will not only change A to 255 or 254, it will also set the carry flag to ONE. So that the effect of SBC HL,DE would then be to assign HL a value of HL-DE-1, NOT HL-DE. In the second example, the instruction DEC A is used twice. DEC will not change the carry-flag, so it will still be zero when the instruction SBC HL,DE is reached, and the subtraction will then go ahead correctly.

Got it? INC and DEC do not alter the value of the carry flag - the other arithmetic instructions do. The other instructions we've covered are RET and LD. Neither of these will alter CARRY at all.

0B        DEC BC  1B        DEC DE  2B        DEC HL  3D        DEC A   05        DEC B  0D        DEC C  15        DEC D  1D        DEC E  25        DEC H  2D        DEC L  

In this chapter we have dealt with how to load machine language programs, and how to run them. The use of the instruction RET and LD were explained, and the arithmetic instructions ADD, ADC, SUB and SBC were introduced along with INC and DEC. The purpose of the carry flag has been covered, and the instructions SCF (Set Carry Flag) and CCF (Complement Carry Flag) have been mentioned.

You are not expected to remember any of the hex-codes which the computer uses - not even the experts do that! All of the codes are printed in an appendix in the back of the book. All you have to know are the words we use for them - the OPCODES - and what they do.

Before you proceed to chapter four, see if you can tackle some of the following exercises. If you find some of them difficult don't worry about it, just take them slowly, and think clearly.

Enter the following machine language program using HEXLD: You will have to look up the various hex-codes yourself!

LD BC,0  LD HL,0  ADD HL,BC  LD B,H       <- NEW ROM ONLY  LD C,L       <- NEW ROM ONLY  RET  

Now use the direct command PRINT USR 30000 to run it. What did you get? If you got zero well done. If, on the other hand, you got -31004 or 34532 then you did something fundamentally wrong. The instructions LD BC, and LD HL, both need THREE bytes altogether to make them work, not two. What instructions did you really give the computer to make it come up with -31004 or 34532? And how exactly did it arrive at the answer? Now try again until you get zero.

Delete HEXLD by typing NEW (or on the OLD ROM by deleting each line individually). The machine code program will STILL BE THERE. Type in the following BASIC program:

10 INPUT A  20 INPUT B  30 POKE 30001,A-INT (A/256)*256  40 POKE 30002,INT (A/256)  50 POKE 30004,B-INT (B/256)*256  60 POKE 30005,INT (B/256)  70 PRINT A,B  80 PRINT USR 30000  90 PRINT  100 GOTO 10  

[Download available for 16K ZX81 -> chapter03-replace.p. I have modified this slightly so that RUNning it installs the necessary machine code to 30000 to make it complete and ready to go.]

The BASIC program will replace the second, third, fifth, and sixth bytes of the machine code routine by the values you input in lines 10 and 20. Run the program and input some values to see what happens. Try going outside the range -32768 to 32767.

Now see if you can write a similar program, including a COMPLETELY NEW machine code routine, which will print a TABLE of values of A and B on the screen, and the result of subtracting A from B in each case. Let A and B both take on all of the values from 1 to 10 inclusive.

Write a machine code routine which will produce a one if BC is greater than or equal to DE, and a zero otherwise. How could you test this? (HINT: see previous exercises on this page). Do so.

Write a short machine code routine which will set the carry flag equal to one, but without altering any of the registers. Do it WITHOUT using the instructions SCF, CCF, or ADD A,0.

 

 

 

 

Peeking and Poking and More About Loading

PEEKING AND POKEING AND MORE ABOUT LD-ING

For those of you who thought maybe seven registers might not be enough, it's just as well we can PEEK and POKE, and thus make use of all the addresses in the RAM (The RAM, which stands for Random Access memory by the way, is the portion of memory which we are allowed to alter - the addresses numbered from 16384 upwards. The add-on 16K pack is RAM for instance). If there's any number we have to store somewhere, either permanently or temporarily, then it makes sense to just POKE that number somewhere - (almost anywhere will do) then when we need it again all we have to do is to PEEK at that address and voila - there it is!

A LESSON IN PEEKING

If you've ever seen any machine language printed anywhere, you may have wondered why obscure brackets kept turning up here and there. What, for example, is the difference between LD HL,16396 and LD HL,(16396)?

It's not just for variety, or to make it look pretty, they do actually mean something: brackets around a number or register-pair will refer to the contents of the ADDRESS in the brackets.

So   LD HL,16396    means  LET HL=16396    and  LD HL,(16396)  means  LET HL=PEEK 16396+256*PEEK 16397  

The second example may have confused you. The only address in brackets is 16396, so how does 16397 come into it? What happened is a kind of side-effect. H and L can each hold ONE BYTE, so the pair HL stores TWO BYTES altogether. The address 16396 only holds ONE byte, so another one has to come in from somewhere. In practice this other byte comes from the next possible address, in the above case, 16397. The real effect of the instruction LD HL,(16396) is LET L=PEEK 16396, followed by LET H=PEEK 16397.

There is also a reverse instruction, which is

LD (16396),HL  

This is effectively POKEing. The result of the instruction is

POKE 16396,HL-INT (HL/256)*256  POKE 16397,INT (HL/256)  

or if you think of H and L seperately:

POKE 16396,L  POKE 16397,H  

In BASIC, this particular pair of instructions is used quite frequently. I'll give you an example. Suppose you've just written a BASIC program, and you want to know how long it is. You can find out the number of bytes your program occupies by using the expression PEEK 16404+256*PEEK 16405 to find the address of the END of your program (including the screen and all of your variables) and then subtract 16509 (the START of your program) from this number. There is a similar expression for the OLD ROM, which is PEEK (16394)+256*PEEK (16395)-16424. A very simple machine code program to calculate this value would be:

OLD ROM                    NEW ROM  112840    LD DE,16424      117D40    LD DE,16509  2A0A40    LD HL,(16394)    2A1440    LD HL,(16404)  C600      ADD A,0          C600      ADD A,0  ED52      SBC HL,DE        ED52      SBC HL,DE  C9        RET              44        LD B,H                             4D        LD C,L                             C9        RET  

The instruction ADD A,0 is used to set the carry flag to zero, so that the immediately following instruction will always produce the correct answer. Remember that there is no such instruction as SUB HL,DE so if we ever need to subtract HL from DE we are forced to use SBC instead. This won't subtract properly unless CARRY equals zero.

Notice how the hex-code for LD HL,(16404) is built up. The first byte is 2A. Now, although you're not expected to remember this, the last time we used a LD HL instruction the code was 21 (hex). The difference is the BRACKETS! LD INSTRUCTIONS WHICH USE BRACKETS HAVE A COMPLETELY DIFFERENT HEX-CODE. The next two bytes are 14h and 40h:- this is the number 16404 in hexadecimal - if you divide 16404 by 256 you get sixty-four (40h) remainder twenty (14h). In the HEX-CODE these two bytes have been switched around to give 1440 rather than 4014. You must always remember to do this in machine code.

If you store this machine code program above RAMTOP (this is something that only NEW ROM users can do easily) as I've described then you can type in or LOAD any BASIC program and find its length in bytes simply by the now familiar direct command PRINT USR 30000.

16404 will ALWAYS contain the address of the end of all the variables in your program - this is its job. It is one of the SYSTEM VARIABLES which are used to help the ROM know what it is doing. If you alter this value by POKEing or LDing then the poor machine will get very confused, although, as we shall see later, this is sometimes an advantage.

Make sure you understand exactly how the above program works, and why every line is needed. The most important instruction is still the first one we learned - RET. If any of the others are missing then you will get the wrong answer, but at least you'll get AN answer. Without RET the program will CRASH.

Not all of the variables (registers) can be LDed from addresses. The instructions you are allowed to use, together with their codes, and a breakdown of exactly what they do, are listed here.

PEEKING  3A        LD A,(pq)     LET A=PEEK pq  ED4B      LD BC,(pq)    LET C=PEEK pq                          LET B=PEEK (pq+1)  ED5B      LD DE,(pq)    LET E=PEEK pq                          LET D=PEEK (pq+1)  2A        LD HL,(pq)    LET L=PEEK pq                          LET H=PEEK (pq+1)    AND POKEING  32        LD (pq),A     POKE pq,A  ED43      LD (pq),BC    POKE pq,C                          POKE pq+1,B  ED53      LD (pq),DE    POKE pq,E                          POKE pq+1,D  22        LD (pq),HL    POKE pq,L                          POKE pq+1,H  

You will notice that only the variable [(register)] A may be assigned a PEEK value, or POKEd anywhere, by itself - all of the other registers may be used in pairs. Usually this is quite a useful feature, but there are times when you'll want to assign a single register (a usual choice is L) without disturbing the value of A. There isn't really any way around this I'm afraid, but what you can do is to assign both halves of a register pair as described above, and then reset one of the registers to zero afterwards.

Suppose you needed to know how far down the screen the PRINT position was. If you look in your instruction manual you'll find that PEEKing 16442 will tell you exactly that (on the OLD ROM you'll need 16421 instead). The problem is to LD this into HL, because the number we're after is ONE BYTE long - it ISN'T stored in either 16441 or 16443 - and one way of doing it is this:

OLD ROM                    NEW ROM  2A2540    LD HL,(16421)    ED4B3A40  LD BC,(16442)  2600      LD H,0           0600      LD B,0  C9        RET              C9        RET  

As you can see, the first instruction will successfully load the contents of 16421/16442 into the L or C register as required, but it will also load H or B with 16422/16443, so H or B must be reset to zero before we return to BASIC, otherwise the figure printed by the routine will be virtually meaningless.

The other way of getting PEEK 16442 into BC is to go via the A register, since this register can be LDed directly all by itself. But as you will see this offers no advantages, since we still have to reset B to zero anyway.

OLD ROM                   NEW ROM  3A2540    LD A,(16421)    3A3A40    LD A,(16442)  2600      LD H,0          0600      LD B,0  6F        LD L,A          4F        LD C,A  C9        RET             C9        RET  

If you still aren't convinced that the second instruction is necessary try omitting it to see what happens. You'll find you get the number 29952 added to the real answer. Can you see why? You started off with the number 30000 and only altered the LOW part. The HIGH part was unchanged (the HIGH part is INT (30000/256)). It happens to be 117. The factor of 29952 comes in because 117*256 is 29952.

Both of the above programs, as they are written, will have the same effect - they will tell you the line number of the PRINT position, that is, they will tell you how far down the screen the next character to be printed will be.

Try feeding in ONE of the above two programs, and then type in this BASIC program:

10 FOR I=0 TO 20  30 PRINT USR 30000  50 NEXT I  

Remember, only NEW ROM users may type NEW without wiping out the machine code. Run it and see what happens. Now insert more lines

20 FOR J=0 TO 3  30 PRINT TAB (8*J);USR 30000;  40 NEXT J  

and again, RUN it and see what happens. OLD ROM users should replace the new line 30 by PRINT USR(30000), (i.e. with a comma at the end of the satement).

[Download available for 16K ZX81 -> chapter04-printpos.p. I have modified this slightly so that RUNning it installs the necessary machine code to 30000 to make it complete and ready to go.]

POKEING IN MACHINE CODE

POKEing is just as easy. To put line 50 of your BASIC program at the top of the screen at the next automatic listing you can POKE 16419,50 (on the OLD ROM it is POKE 16403,50). You must make sure the cursor is 50 or more first though. In machine code:

OLD ROM                   NEW ROM  3E32      LD A,50         3E32      LD A,50  321340    LD (16403),A    322340    LD (16419),A  C9        RET             C9        RET  

Note that it doesn't actually matter what number returns to BASIC - (in actual fact it will be 30000) - the important thing is that the system variable called S-TOP (Screen Top) is POKEd with 50. That is what this program does.

Now look at the HEX-CODE of LD (16419),A. The first byte is 32h. This is the code for LD (pq),A where pq represents some arbitrary address. The remainder of the code is 2340, which is the number 16419 in hexadecimal (with of course the first and last bytes switched around). So even though we humans would write our OPCODE with the (16419) first, and the ,A second, the machine language code always puts the instruction itself FIRST - despite the fact that the instruction itself actually incorporates the A at the end of the OPCODE. You must not put the 32h last, for the instruction 234032 would mean something totally different. In fact it would probably end up crashing, because it would take it to mean

23        INC HL  40        LD B,B  32        LD (????),A  

with the (????) address made up of your next two bytes of machine code.

There are some other PEEK and POKE instructions which use register names throughout. These are:

0A        LD A,(BC)    LET A=PEEK BC  1A        LD A,(DE)    LET A=PEEK DE  7E        LD A,(HL)    LET A=PEEK HL  46        LD B,(HL)    LET B=PEEK HL  4E        LD C,(HL)    LET C=PEEK HL  56        LD D,(HL)    LET D=PEEK HL  5E        LD E,(HL)    LET E=PEEK HL  66        LD H,(HL)    LET H=PEEK HL  6E        LD L,(HL)    LET L=PEEK HL    02        LD (BC),A    POKE BC,A  12        LD (DE),A    POKE DE,A  77        LD (HL),A    POKE HL,A  70        LD (HL),B    POKE HL,B  71        LD (HL),C    POKE HL,C  72        LD (HL),D    POKE HL,D  73        LD (HL),E    POKE HL,E  74        LD (HL),H    POKE HL,H  75        LD (HL),L    POKE HL,L  

If you study the codes of the instructions that have (HL) in them you'll see that they form a regular pattern. In fact it looks very much like there ought to be an instruction LD (HL),(HL) with code 76 just to fill up a small hole in the regular pattern. In actual fact there is no such instruction, and code 76 corresponds to an instruction called HALT.

To demonstrate what I mean, here is a small table of all of the LD codes, which use registers A to L, and address (HL):

+-----+---------------------------------+  | LD  |  B   C   D   E   H   L  (HL) A  |  +-----+---------------------------------+  | B   |  40  41  42  43  44  45  46  47 |  | C   |  48  49  4A  4B  4C  4D  4E  4F |  | D   |  50  51  52  53  54  55  56  57 |  | E   |  58  59  5A  5B  5C  5D  5E  5F |  | H   |  60  61  62  63  64  65  66  67 |  | L   |  68  69  6A  6B  6C  6D  6E  6F |  |(HL) |  70  71  72  73  74  75  --  77 |  | A   |  78  79  7A  7B  7C  7D  7E  7F |  +-----+---------------------------------+  

Do you see what I mean about a regular pattern with LD (HL),(HL) missing? Of course, it's not an instruction you'll ever want to use, since it does absolutely nothing, but it's worth pointing out that you must never even ATTEMPT to use it because, as I've said, 76 is the code for HALT.

Why is any variable in brackets a register pair rather that a single register? Why is any variable NOT in brackets a single register rather than a register pair? If HL contained a value of 16434, what is the difference between LD B,(HL) and LD BC,(16434)? What is the precise effect of each? See if you can write a program in machine language which will assign to HL a value of PEEK 16442 ONLY, using one of the LD ,(HL) instructions.

We have now covered all of the basic LD instructions which operate on the registers A, B, C, D, E, H, L. We shall now take a look at some of the other ways of loading these variables.

HOW TO LOAD BLOCKS

Loading BLOCKS means loading huge chunks of memory all in one go. For example, if you had a machine code routine stored beginning at location 30000 and you wanted to move it completely to location 20000, then if you were really really patient you could write a new machine code routine along the lines of

11204E    LD DE,20000  213075    LD HL,30000  7E        LD A,(HL)  12        LD (DE),A  23        INC HL  13        INC DE  7E        LD A,(HL)  12        LD (DE),A  23        INC HL  ....      ....            and so on.  

You could shorten things a bit if you knew about the instruction LDI, which means LOAD WITH INCREMENT. This is a very special instruction which does four things all in one go. First of all it will transfer the contents of the ADDRESS stored in HL into the ADDRESS stored in DE, then it will increment both HL and DE, and it will decrement BC. It will not alter the value of register A. To summarise:

EDA0      LDI    POKE DE,PEEK HL                   LET HL=HL+1                   LET DE=DE+1                   LET BC=BC-1  

The above program could therefore have been completely rewritten as

11204E    LD DE,20000  213075    LD HL,30000  EDA0      LDI  EDA0      LDI  EDA0      LDI  ....      ....            and so on.  

There is no list of variables after the opcode LDI, because the instruction will ALWAYS load from (HL) ro (DE). You must not write LDI (DE),(HL) because this does not make sense. Further, it is impossible to load in this manner in any other combination. Loading from (HL) to (BC) for example simply cannot be done in a single instruction.

There is also an instruction LDD, or LOAD WITH DECREMENT, ehich has the same effect as LDI except that DE and HL are decremented and not incremented. Neither of these instructions, as with all LD instructions, will in any way alter the value of CARRY. The code for LDD is EDA8.

REPEATING THINGS

Even with LDI and LDD at our disposal, it would still be a very tedious affair to move something from, say, 30000 to 20000 if that something were around fifty bytes long. If it were a hundred we'd probably give up in despair. Fortunately for us both LDI and LDD have a REPEAT facility. If, instead of writing LDI we wrote LDIR, with the extra R standing for REPEAT, then the instruction LDI would be carried out over and over again, and would not stop until the value of BC was zero. So if the routine we wanted to move was in fact 100 bytes long then we could move it using the routine

016400    LD BC,100  11204E    LD DE,20000  213075    LD HL,30000  EDB0      LDIR  

When the machine reaches the instruction LDIR, BC will contain a value of 100. After LDI had been carried out once, the first byte would have been transferred, DE would be increased to 20001, HL would be increased to 30001, and BC would be decreased to 99. After a second attempt, the second byte would have been transferred, and BC would contain a value of 98. After LDI had been carried out one hundred times, the whole routine would have been successfully transferred, and BC would contain a value zero and so the program would continue with the next instruction. If this routine were the entire program then the next instruction should of course be RET.

The four instructions LDI, LDD, LDIR, LDDR each do slightly different things. Make sure you understand the differences between them. They also each have a different code, all beginning with ED. The codes are

EDA0      LDI  EDA8      LDD  EDB0      LDIR  EDB8      LDDR  

I shall now give you a program which will enable you to SCROLL the screen BACKWARDS, so that the screen moves downwards, not upwards, and the print position is moved to the top of the screen.

It will work on the OLD ROM provided [that:]

  • All twenty-two lines of the screen are full, i.e. contain thirty-two characters plus a newline character.
  • You do not attempt to PRINT anything again (however you can alter the screen by POKEing the display file).

It will work on the NEW ROM provided [that:]

  • RAMTOP is at least 19712 (effectively this means if you have 4K or more plugged in).
  • Every time you use the statement SCROLL you fill the bottom line (for example by using the statement PRINT "thirty-two spaces", your next PRINT should be a PRINT AT[)].

A complete explanation of the program will also be given.

01D602    LD BC,726  2A0C40    LD HL,(16396)  09        ADD HL,BC  54        LD D,H  5D        LD E,L  01B502    LD BC,693  2A0C40    LD HL,(16396)  09        ADD HL,BC  EDB8      LDDR  C9        RET  

The screen may now be scrolled BACKWARDS by using the NEW ROM statement PRINT AT USR 30000,0; On the OLD ROM the corresponding statement is LET L USR(30000) but remember that on the OLD ROM once the screen is full you can only "PRINT" by POKEing into the display file. The machine code routine will leave a value of zero in BC (see the description of the last instruction, LDDR) so having executed the machine code it will then PRINT AT 0,0; i.e. it will move the NEW ROM print position to the top of the screen. This is precisely the opposite of SCROLL.

The first instruction is LD BC,726. This is the number of characters in the screen. There are twenty-two lines and each line contains thirty-three characters (thirty-two plus one new-line character) hence the total number is 22*33=726. The address 16396 (together with 16397) contains the address of the START of the display file (the first character in the display file is a new-line, so the screen itself actually starts one character further on). This address is LDed into HL. Remember that LD HL,(16396) will load TWO bytes into HL, not one. The ADD instruction will then calculate the address of the LAST byte of the screen.

In order for LDDR to work, we need this address in DE, not in HL, and so since LD DE,HL is not a valid instruction it needs TWO instructions, LD D,H and LD E,L to accomplish this. We can now use HL for something else.

We need the address of what WILL BE the last character of the screen after we've finished scrolling (or antiscrolling if you want to call it that). Since it is the bottom line that will be lost, then this will be the last character of what is currently the TWENTY-FIRST line. So we need the start address plus 21*33, or 693.

The next three instructions in the program: LD BC,693; LD HL,(16396); and ADD HL,BC will achieve this, and the result will be left in HL. This is precisely what we need for LDDR to work. LDDR will transfer from the address contained in HL to the address contained in DE, i.e. it will move the last character of the twenty-first line to the last character of the twenty-second line, before HL and DE are both decremented, or decreased by one.

How many times do we need to make such a transfer? We have to move twenty-one lines altogether, so we have to make sure that we do not use LDDR until BC contains a value of 21*33, or 693. As it happens, it already does, since we assigned it to 693 earlier on in the program. We may now quite happily use the instruction LDDR to BLOCK LOAD the first twenty-one lines of screen down to their new position occupying the LAST twenty-one lines of screen. Note that the old screen will be completely overwritten by the new screen with the exception of the first (top) line, which will be left unchanged. This is why the BASIC statement PRINT AT 0,0;"thirty-two spaces" is needed after every antiscroll.

The following NEW ROM BASIC program is designed to demonstrate the ANTISCROLL feature at work. It isn't a terrifically exciting game, or a pattern making artistic genius, or anything, but it will show you exactly what the machine code we've just been working on will do. You can of course insert the routine into any program - there are some graphics games which would be immensely enhanced by the ability to SCROLL in either direction. This program sets up a striped pattern across the screen, with each stripe composed of a random character chosen from the whole ZX81 set. The pattern on the screen will then wait for you to tell it what to do. Pressing the "up" key will move the pattern upwards, and pressing the "down" key will move the pattern downwards. These are of course the standard cursor control keys I'm referring to, except that you don't need to use SHIFT.

The listing is written for both FAST and SLOW modes. In FAST, line 110 should read PAUSE 40000, but in SLOW it should be changed to IF INKEY$="" THEN GOTO 110. Otherwise enter the program as listed.

UP AND DOWN

10 DIM A$(22,32)  20 FOR I=0 TO 22  20 FOR I=1 TO 22  30 LET B$=CHR$ (63*RND+128*(RND<.5))  40 FOR J=1 TO 5  50 LET B$=B$+B$  60 NEXT J  70 LET A$(I)=B$  80 PRINT A$(I)  90 NEXT I  100 LET A=1  110 PAUSE 40000  120 LET B=A+1  130 IF B=23 THEN LET B=1  140 LET C=A-1  150 IF C=0 THEN LET C=22  160 LET B$=INKEY$  170 IF B$="6" THEN PRINT AT USR 30000,0;A$(C)  180 IF B$="7" THEN SCROLL  190 IF B$="7" THEN PRINT A$(B)  200 IF B$="6" THEN LET A=C  210 IF B$="7" THEN LET A=B  220 GOTO 110  

[Download available for 16K ZX81 -> chapter04-antiscroll.p. I have modified this slightly so that RUNning it installs the necessary machine code to 30000 to make it complete and ready to go.]

EXERCISES

Based on the Antiscroll program in this chapter, write a machine language program to SCROLL forwards, as the keyboard SCROLL does (this exercise is especially useful if you do not have SCROLL on your keyboard). Then see if you can write a machine language program which scrolls forward, but which will ONLY SCROLL THE BOTTOM HALF OF THE SCREEN, so that the top ten lines are unaltered, the eleventh line is lost, and the twelth to twenty first lines are all moved up one line.

Write a BASIC program making use of the routine. You will need the BASIC statement PRINT AT 21,0;"thirty-two spaces" every time the machine code routine is used. Try leaving this out just to see what happens.

If you can't cope with the challenge of writing such a SCROLL program, then I'll give you a hint or two. You will need to use LDIR instead of LDDR, otherwise all you'll get is a pretty pattern, and you'll need to start block loading at the BEGINNING of the screen, NOT the end. The instruction LD HL,(16396) will always give you the address at which the screen begins. Don't forget that a full line contains thirty-three characters, not thirty-two, since there is always a new-line character there as well.

 

 

 

More Places to Store Machine Code

SOME NEW PLACES TO STORE MACHINE CODE

Storing machine code above RAMTOP will protect it from being erased by NEW, or overwritten by a program, but it has the disadvantage that you can never save it. There are several alternative locations in which we can store machine language programs, and we shall explore a few of the possibilities in this chapter.

USING REM

To store a machine language routine that is fifty bytes long, make the first line of your program

1 REM 12345678901234567890123456789012345678901234567890  

i.e. a REM statement with fifty characters after it. If your routine was sixty bytes long then you'd need sixty characters after the word REM. If it were only three bytes long you would only need three characters after the word REM. It doesn't actually matter what these characters actually are, but counting upwards in ones, as I have done, will ensure that you don't lose count halfway through. You will need to LOAD "HEXLD" before you add this new line one, and then change line 10 to

10 LET X=16514  (or 16427 on the OLD ROM)  

[Download available for 16K ZX81 -> chapter05-hexldremlo.p]

OLD ROM users should ensure that line one does not appear on the automatic LISTing. You can use the command POKE 16403,10 to remove it. If this has no effect try moving the cursor to line 10 and try again.

Now you can enter a machine code program exactly as before, except that to execute it you must say USR 16514 instead of USR 30000. On the OLD ROM you must say USR(16427). BUT you MUST NOT type NEW. Delete HEXLD by entering the line numbers one at a time, and do not delete line one! On the OLD ROM you must not even attempt to list line one or you may cause a crash.

Now there are two very important differences between using 16514 and using 30000. Firstly, SAVE will store the machine code as well as the BASIC program - this is something you cannot do in upper memory. Secondly, the command NEW will erase it. It is thus an integral part of the program, and can only be used with that one BASIC program and no other (unless you delete it line by line and then type in a new program line by line). If you have written a machine code routine specifically to accompany some BASIC program then this method is an obvious choice, but it does have one big disadvantage - on the OLD ROM the command LIST will usually cause a system crash.

There is another very very good place to store machine code, that is immediately after the program area. This has several advantages:

  • The BASIC surrounding program can be safely listed - even on the OLD ROM.
  • The MACHINE CODE can be SAVED.
  • Using RUN, as opposed to GOTO 1, will not wipe it out.

To load a machine code routine that is, say, 20 bytes long, type the following BEFORE you type in any BASIC:

OLD ROM:  1 REM 45678901234567890  NEW ROM:  1 REM 678901234567890  

Then as a direct command type:

OLD ROM:  POKE 16424,-1  NEW ROM:  POKE 16509,-1  

You have now reserved a space of twenty bytes in which to store whatever machine code you like. The starting address is a little more complicated though - it is:

OLD ROM:  PEEK(16392)+256*PEEK(16393)-20  NEW ROM:  PEEK 16396+256*PEEK 16397-20  

The PEEK expression is the end of the machine code, and the minus twenty is there to find the start. This is an excellent way of storing machine language routines. You begin loading it from address PEEK 16396+256*PEEK 16397-length-of-routine, and you can execute it with the expression USR (PEEK 16396+256*PEEK 16397-length-of-routine). First though, there is one disadvantage to get round. As I've explained things so far, there is no way you can actually load an editing program like HEXLD: If you LOAD before you apply the above technique then HEXLD will disappear along with the REM statement as soon as you POKE 16509. If you try to LOAD after you've reserved a space then the very act of LOADing will overwrite this space.

Here then is a step by step method of reserving space for machine code in a place that is 1) editable, 2) SAVEable, and 3) UnLISTable.

STEP ONE. LOAD an editing program such as HEXLD.

STEP TWO. Add a new line at the END of the program: 9999 REM followed by a number of arbitrary characters. On the OLD ROM you'll need three characters less than the number of bytes in the machine code routine, on the NEW ROM you'll need five bytes less than the machine code. The best way of doing this is to fill the REM statement with digits, and simply start counting from 4 (OLD ROM) or 6 (NEW ROM). Like this - for a fifteen byte routine:

OLD ROM:  9999 REM 456789012345  NEW ROM:  9999 REM 6789012345  

Of course it doesn't actually matter if you have too many characters, but it is a waste of space if you reserve [an] area and then don't use it.

STEP THREE. Add the following lines anywhere in the program. I've put them at 9000, but it doesn't matter. If you use 8000 then just remember to read 8000 every time you see 9000 written on this page.

OLD ROM:  9000 LET X=PEEK(16392)+256*PEEK(16393)  NEW ROM:  9000 LET X=PEEK 16396+256*PEEK 16397    OLD ROM:  9010 POKE X-(four more than the number of                 characters in the REM statement),-1  NEW ROM:  9010 POKE X-(six more than the number of                 characters in the REM statement),-1    BOTH:     9020 STOP  

[Download available for 16K ZX81 -> chapter05-hexldremhi.p]

If you counted up to fifteen in line 9999 (as above) then 9010 should be POKE X-16,-1. If you counted up to twenty then line 9010 should instead be POKE X-21,-1, and so on. Remember though to start counting at four or six though, as above.

STEP FOUR. Run the program from line 9000, and then delete lines 9000, 9010, and 9020.

STEP FIVE. Replace all references to the machine-code-starting-address in your editing program by the expression PEEK 16396+256*PEEK 16397 minus the number you counted up to in the REM statement. OLD ROM users should instead use PEEK(16392)+256*PEEK(16393) minus the number you counted up to in the REM statement.

You are now complete. The only thing you must not do is type NEW, since this will erase the machine code. Other than that you are in complete command.

REM STATEMENTS

For the purpose of storing machine code, OLD and NEW ROM REM statements are completely different. Let's examine them one at a time. First of all for the OLD ROM:

There are several important points about OLD ROM REM statements. Most people already know that a "blank" REM statement - that is a statement consisting of the word REM and nothing else - has the effect of ensuring that the next line is not executed. It is therefore the same as GOTO the-line-after-next, and can be used in BASIC programs deliberately with this meaning.

The biggest limitation of an OLD ROM REM statement is the fact that you may not store the byte 76 (hex) in the line, except in extremely limited cases, which I shall explain. The reason is that a character 76 is interpreted by the ROM as an end of line marker. The two bytes immediately after such a character will be interpreted as representing the line number of the next BASIC program line, and the following byte will be the first character in that line. Thus if the following data were POKEd into a REM statement in line one the following would happen:

DATA:      39 76 01 01 F8 E4 D5    RESULT:    1 REM T           257 LET < THEN             2 next lines of program...  

If you tried to RUN this program you would get a syntax error in "line 257". Typing RUN 2 would be useless, because the program searches for line numbers from top to bottom, and as soon as it hit the "line number" 257 it would think to itself "ah - there obviously isn't a line 2 in the program - I'll have to RUN it from here instead". The same applies to all GOTOs in the program which have destinations between 2 and 257. You must only allow 76s in your data IF the next two bytes form a "line number" less than the next line number in your program, and IF you never try to execute this "new line".

On the other hand - this treatment does offer one or two advantages. For instance, if you made your REM statement too long and you want to shorten it, if your machine code data ends at address A just type

POKE A+2,2  POKE A+1,0  POKE A,118  

then simply delete "line 2" by typing in its line number. It doesn't matter if there is already a line numbered 2 in the program - typing the line number alone will only delete the first "line 2" in the program - all your excess REM characters in other words.

Conversely, if you find you don't have enough characters after the word REM just type in a line 2 consisting of a second REM statement full of arbitrary characters. In this way as soon as the "real" end of line marker is overwritten, line 2 will become part of line 1, with enough characters for whatever you need.

Alas, the NEW ROM does not fit any of these descriptions. NEW ROM REMs are quite, quite different.

The first, and most important difference, is that you can put 76s into the REM data and the machine won't notice. BUT if you do so be prepared to be confused by the LISTing - even the ROM gets confused over it - but you don't need to worry because even with supposed new-line markers in mid-line the program will RUN quite smoothly, and will not interpret the remainder of the line as a different line.

On the other hand, it's a little more difficult to extend the length of a REM statement. If you want to overrun into line two you'll have to do some clever POKEing first, but I'll explain how to get round that in a minute. The obvious way of making a line longer is simply to use EDIT and add more characters. Unfortunately for us this is usually not a very wise thing to do.

If the data in the line does not contain a byte 7E then by all means go ahead and use EDIT - you are quite safe, and nothing will go wrong.

If the data in the line does contain a byte 7E then DO NOT use EDIT. In the listing, a byte 7E is invisible, and the five bytes of data that follow immediately after it will also be invisible, but they are still there! If on the other hand you use EDIT, all six of these invisible bytes will simply vanish without a trace.

7E is used by Sinclair to mean "This is a (floating point) number". Whenever you use a decimal number in a program listing the ROM will automatically follow this number with a byte 7E, followed by five more bytes which contain the number itself in floating-point-binary-form. Both the byte 7E and the five bytes that follow will be invisible from the listing. This is what causes all the problems in editing REM statements. Now although I agree that this is a very very efficient means of storing floating point numbers in a program, it is also true that Sinclair Research could have used ANY byte for this purpose - they didn't specifically have to use 7E. It is of course the purest of coincidences that 7E happens to be one of the most commonly used machine language instructions of all.

The only practical means of adding more characters to a REM statement containing machine code on the NEW ROM is to let the data overrun into line two, but there are problems even there, thanks to our kind friends at Sinclair Research. You see the start of every line of program is preceded by two invisible bytes which store the length of the line, so that even if you overwrite the end-of-line-marker, the ROM will still try to interpret the second line from the same point. To get round this you have to actually POKE these invisible bytes with different values. The following is a small routine which will enable you to increase the length of a REM statement at line one.

Step one is to insert a new line 2 [in]to your BASIC program consisting of the word REM followed by a number of arbitrary characters. Then, at ANY point in the program insert the following five lines - (they will shortly be deleted anyway):

LET A=16515+PEEK 16511+256*PEEK 16512  LET A=A+PEEK A+256*PEEK (A+1)-16511  POKE 16511,A-256*INT (A/256)  POKE 16512,INT (A/256)  STOP  

Simply run this routine and line 2 will automatically be a part of line 1. You can delete this routine now - its job has been done. LIST line one - you'll see that line two still looks quite seperate, but try moving the cursor down - you'll find it skips over line two altogether. Try deleting line 2 by typing in its line number - it won't work because now the computer doesn't know that line 2 is there! Whatever the listing may look like, the ROM will now ignore line 2 altogether, taking it to be part of line one. You may now quite happily overwrite the end-of-line-marker at the end of line one with no ill effects.

Conversely, the following routine will shorten a REM statement by a minimum of six bytes.

LET A=the address of the last byte which you wish to preserve        in the REM statement of line 1  LET B=A-16511  LET C=PEEK 16511+256*PEEK 16512-B-4  POKE 16511,B-256*INT (B/256)  POKE 16512,INT (B/256)  POKE A+1,118  POKE A+2,0  POKE A+3,2  POKE A+4,C-256*INT (C/256)  POKE A+5,INT (C/256)  STOP  

Again you simply RUN the routine once, and then delete it. Now LIST the program and you'll find a new line 2 has appeared. Delete this by typing its line number and your REM statement will now be as short as you need it.

USING THE VARIABLES AREA

Another place where machine code may be stored is in the variables area. To do this you must first of all reserve the space. To store a machine code routine of n bytes (n is the length) OLD ROM users should type DIM O(n/2), and NEW ROM users should type DIM O$(n). You may now write your machine code.

On the OLD ROM the starting address will be PEEK(16392)+256*PEEK(16393)+2 provided the array O is the first item in the variables area. This will be the case if the DIM was the first DIM, FOR, INPUT, or LET statement executed since the last time you used RUN or CLEAR. If you DIMensioned O as a direct command you should remember to type CLEAR first. You can say in your program something along the lines of LET A=PEEK(16392)+256*PEEK(16393)+2 right at the very start, and this will not change throughout the program.

On the NEW ROM the starting address is PEEK 16400+256*PEEK 16401+6 provided the character array O$ is the first item in the variables area. This will be true if the DIM was the first DIM, FOR, INPUT, or LET statement executed since the last time you used RUN or CLEAR. You can dimension O$ as a direct command, but you must remember to type CLEAR first. There is however one big difference between the OLD and NEW ROMs here. On the NEW ROM the value PEEK 16400+256*PEEK 16401+6 will change during the running of your program if you have less than 3.25K plugged in. If you have more than 3.25K then you don't need to worry, but otherwise you must recalculate the expression every time you wish to access the machine code.

One last important point is that having stored machine-code in the variables area, any future use of either RUN or CLEAR will completely wipe it all out, never to be seen again. For this reason I do not advise using it for machine code storage. It WILL SAVE and RE-LOAD, again provided you never type RUN or CLEAR.

 

 

 

Stacking and Jumping

THE STACK

There is an area of RAM that is set aside for storing various pieces of information to help the machine know what it's doing. It works like this:

The word "stack" is something that the computer people have got straight out of a dictionary. It means exactly what it sounds like! I magine a stack of cardboard boxes. Each box is really a memory location, so each has an address, but if you want to know what's in any particular cardboard box then the only one you can easily look at is the top one. If you tried to pull one of the boxes from somewhere in the middle then all the boxes above it would fall down. Conversely, to add a new box to the stack, the only place you can easily put it is at the top.

The memory locations in the stack are just like that. You can put things on top of it, but ONLY at the top, and you can take things FROM THE TOP. There are two special words that go with the stack - one word which means "stacking a new number onto the top", and a second word that means "removing a number from the top". The first word is PUSH, and the second word is POP, so if you PUSH the number five onto the stack, and then you PUSH the number one-thousand, and then you PUSH say 16426, the first number you can POP is 16426, because this number is at the top since it was put there last. The next number to be POPped will be 1000, and then five.

The stack is stored very very high in the address [range], so that there is less chance of programs "colliding" with the stack as either one or the other is built up. In the OLD ROM the bottom of the stack is at the very top of memory - 17407 for 1K, 20479 for 4K, and 32767 for 16K. In the NEW ROM the whole stack moves around - the bottom of the stack is at an address stored in one of the system variables - ERR_SP - to be found at 16386 and 16387. The stack is actually very perculiar, because it's UPSIDE DOWN. The BOTTOM of the stack is at the TOP of available memory, and the TOP of the stack is BELOW it! It turns out to be more efficient this way. It's not actually a deliberate plot to confuse the whole human race so that the world may be taken over by ZX computers, even if it does at times seem like it. So remember - the stack, or the MACHINE STACK as it's sometimes called, is like a stack of cardboard boxes piled up on a shop floor, except that in a daring feat of defiance of Newton's laws this stack instead decides to reside on the ceiling and build up downwards. The top - the only part you can easily get at - is lower down than the bottom!

The stack is so important to the computer that a special REGISTER is set aside just to store the position of the TOP of the stack (the part with the lowest address - the part we can get to). That register is called SP, which stands for STACK POINTER. It is actually a register-PAIR, because it can store two seperate bytes, but unlike the other register-pairs BC, DE, and HL, we CANNOT treat the two halves independently - they just won't seperate.

Here's how the instructions PUSH and POP work. Suppose HL contained a value 12345. This means that H contains a value of INT(12345/256), or 48, and L contains a value of 12345-256*INT(12345/256), or 57. Now the instruction PUSH HL would store the number 12345 at the top of the stack. It would do it by first of all stacking the HIGH part, and then stacking the LOW part. It would also alter the value of SP accordingly since two more bytes have been added to the stack, and the position of the top will therefore have moved (down) by two addresses.

It is unfortunately not possible to PUSH single registers onto the stack, you may only PUSH register-pairs, so BC may be PUSHed but B on its own may not. It is worth noting that the instruction PUSH BC will not in any way alter the value of BC, it will simply copy it without changing it. This of course goes for all PUSH instructions.

PUSH can be thought of in BASIC as a sequence of three statements:

PUSH HL    POKE SP-1,H             POKE SP-2,L             LET SP=SP-2  

POP of course works the other way round. POP HL will first of all remove L from the stack, and will then remove H. SP will be changed, since the top of the stack will have moved.

POP HL    LET L=PEEK(SP)            LET H=PEEK(SP+1)            LET SP=SP+2  

Verify by using the BASIC equivalents given, that PUSH HL followed by POP DE is the same thing as LD D,H followed by LD E,L.

PUSH

Here are the codes for the instruction PUSH. One of them will require a small degree of explanation.

F5        PUSH AF  C5        PUSH BC  D5        PUSH DE  E5        PUSH HL  

The register-pair AF, which cannot normally be used in this way, is made up of smaller single registers A and F, in the same way that BC is composed of B and C. A is the register which we've been using throughout the book so far, but F is something completely different. The F stands for FLAGS, and is so called because it stores the value of all the FLAGS used (a FLAG is a memory [location] that can only store zero or one). One of these FLAGS we've already seen - the CARRY flag. The F register has the capability to store eight flags altogether, but in fact only six of them are used. We shall see what these are, and how to use them, later on.

POP

The codes for the POP instruction are very similar to the codes for PUSH. They are:

F1        POP AF  C1        POP BC  D1        POP DE  E1        POP HL  

One of the major uses of PUSH AF and POP AF is simply to put the value of A onto the stack. The fact that F has been stacked with it is irrelevant. PUSH AF will conveniently store the value of A until it's needed again, at which point its value may be recovered by the use of POP AF. This can be useful if you have to use the A register to perform calculations of some kind that couldn't be performed by any other register, but when the value of A will still be needed later on in the program.

For example, to add twenty-five to the value of B without altering the value of any other register:

F5        PUSH AF  78        LD A,B  C619      ADD A,25d  47        LD B,A  F1        POP AF  

Why will only B and no other registers be altered (not even the CARRY flag!)? See if you can work out precisely what the above routine is doing, before you read on.

ALTERING SP

We can actually use SP in much the same way that we use DE and BC. We can add and subtract it, and we can load it. The hex codes are

F9        LD SP,HL  31        LD SP,mn  ED7B      LD SP,(pq)  ED73      LD (pq),SP  39        ADD HL,SP  ED7A      ADC HL,SP  ED72      SBC HL,SP  33        INC SP  3B        DEC SP  

This is very powerful, and very useful. Suppose you wanted to exchange the values of D and E without altering anything else. The following routine will do just that

D5        PUSH DE  D5        PUSH DE  33        INC SP  D1        POP DE  33        INC SP  

The final instruction INC SP was necessary in order to restore the Stack Pointer to its original value. If this is not done you may cause a pretty nasty crash.

SP is not the only very specialised register in use. There is another two byte register called PC, or PROGRAM COUNTER. Its job is to remember whereabouts we are in the program. Every time it has to execute an instruction it will take a look at what PC says. If it says 30004 then it will execute the instruction at location 30004, and then it will increment the value of PC by the number of bytes in that instruction, so that NEXT time round it will be looking at the next instruction in sequence. For example, if 30004 contained the instruction LD A,B then this would be carried out and PC would be increased to 30005. If the instruction at 30005 was LD A,2 then once this was carried out PC would be increased by TWO, since LD A,2 is a TWO-BYTE instruction. PC would then be reading 30007 where the next instruction begins.

If you alter the value of PC then the effect is like a BASIC GOTO. The only difference is that machine code does not use line numbers. The machine language instruction that does this job is JP, which of course is short for JUMP. JP 30000 means GOTO address 30000 and continue executing this machine code program from there. Of course all this instruction REALLY does is to load the number 30000 into register PC (but without incrementing it at the end of the instruction), so that it thinks 30000 is the next address in the program. It is far more useful for us human beings to think of it as kind of GOTO though, because that's what we're used to.

Be careful with JP though. If you create an infinite loop in machine code then TOUGH! You're stuck with it, and what's more you can never break out unless you actually switch the machine off at the mains. Some other computers will let you break out of machine code, but the ZX81 will not, neither will the ZX80. An example of an infinite loop would be

77        30000     LD (HL),A  23        30001     INC HL  C33075    30002     JP 30000  

I've written the actual addresses in the middle column. Usually this isn't done, and important lines are marked with LABELS, or words which tell us which lines do what. These LABELS do not appear in the hex, and we only in fact write them for our own convenience. If for instance we decided to call the first line START then our pretty bad program could be written

77        START     LD (HL),A  23                  INC HL  C33075              JP START  

There is another instruction similar to JP, called JR or JUMP RELATIVE. It means jump forward a given number of bytes. In many ways it is better than JP because it is only two bytes long instead of three, and because a whole routine may be RELOCATED without changing JP destinations all over the place. JR 0 has no effect whatsoever, and the next instruction will be executed in sequence, however JR 1 will cause the next instruction (assuming it to be a single byte instruction) to be skipped. To skip over a two byte instruction, or two single-byte instructions, you will need to use JR 2.

It is also possible to jump backwards using JR, since there is a convention that any hex number greater than 7F will be treated as a negative number, obtained by subtracting 256 from the number it would normally represent. To make life easier I have included a second table of hexadecimal numbers, only this time using the negative sign convention.

+----------------------------------------------------------------------+  |      0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F   |  |   +------------------------------------------------------------------+  | 8 | -128-127-126-125-124-123-122-121-120-119-118-117-116-115-114-113 |  | 9 | -112-111-110-109-108-107-106-105-104-103-102-101-100-99 -98 -97  |  | A | -96 -95 -94 -93 -92 -91 -90 -89 -88 -87 -86 -85 -84 -83 -82 -81  |  | B | -80 -79 -78 -77 -76 -75 -74 -73 -72 -71 -70 -69 -68 -67 -66 -65  |  | C | -64 -63 -62 -61 -60 -59 -58 -57 -56 -55 -54 -53 -52 -51 -50 -49  |  | D | -48 -47 -46 -45 -44 -43 -42 -41 -40 -39 -38 -37 -36 -35 -34 -33  |  | E | -32 -31 -30 -29 -28 -27 -26 -25 -24 -23 -22 -21 -20 -19 -18 -17  |  | F | -16 -15 -14 -13 -12 -11 -10 -9  -8  -7  -6  -5  -4  -3  -2  -1   |  +---+------------------------------------------------------------------+  

Here the number -5 is represented in hex by FB, and so it is therefore possible to use the instruction JR -5, but note that because of this convention we are unable to say JR 129 for instance, because 129 in hex is 81, which would here be taken to mean -127, and would be a jump backwards. The range we are limited to is therefore from -128 to 127.

JR 0, as we have said, does absolutely nothing. It will continue with the next instruction. It is important to remember that all relative jumps are counted from the NEXT instruction. JR 0 means execute the NEXT PLUS ZERO instruction, JR 1 means execute the NEXT PLUS ONE instruction. Consequently if we were to say JR -2 then you must count backwards for two bytes, starting at zero with the NEXT instruction. You will find that two bytes leads you to exactly the instruction we have just executed - the instruction JR -2. JR -2 is therefore an infinite loop, and is not a recommended instruction to use in a program.

The rather silly (infinite loop) program a couple of pages back can now be rewritten in one less byte using JR instead of JP.

77        START     LD (HL),A  23                  INC HL  18FC                JR -4        or JR START  

You have probably now realised that JP and JR are more or less useless on their own, in the same way that the BASIC statement GOTO would be useless if it weren't for IF/THEN statements and GOTO N. We need some kind of a CONDITIONAL jump, so that we can say IF some condition is true THEN jump to a new address pq, otherwise we are virtually certain to produce an infinite loop. Although machine language doesn't have quite the same kind of flexibility as an IF/THEN statement, there are four conditions we can check for using JR, and eight conditions we can check for using JP. These are:

18        JR e       JUMP RELATIVE by e bytes.  28        JR Z e     IF the last result calculated was zero                       then JUMP RELATIVE by e bytes.  20        JR NZ e    IF the last result calculated was non-zero                       then JUMP RELATIVE by e bytes.  38        JR C e     IF CARRY=1 THEN JUMP RELATIVE by e bytes.  30        JR NC e    IF CARRY=0 THEN JUMP RELATIVE by e bytes.  

and for JP:

C3        JP pq       JUMP to address pq.  CA        JP Z pq     IF the last result calculated was zero                        THEN JUMP to pq.  C2        JP NZ pq    IF the last result calculated was non-zero                        THEN JUMP to pq.  DA        JP C pq     IF CARRY=1 THEN JUMP to pq.  D2        JP NC pq    IF CARRY=0 THEN JUMP to pq.  EA        JP PE pq    see below.  E2        JP PO pq    see below.  FA        JP M pq     IF the last result calculated was negative                        (minus) THEN JUMP to pq.  F2        JP P pq     IF the last result calculated was positive                        (plus) THEN JUMP to pq.  

Now although this is a far cry from IF A$ [=] "HELLO" THEN PRINT "GOODBYE" as you're used to, you'll soon see that even this horrendous task may be evaluated in machine code. First though I think I ought to explain about the instructions JP PE and JP PO. The P actually stands for PARITY, and the E and O mean Even and Odd. What we are doing is testing one of the flags - a flag called P/V. It's not all that difficult to understand - it works like this.

P/V stands for Parity/Overflow. V stands for Overflow because O is too confusing - it could mean zero or it could mean Odd (as in JP PO), so in their wisdom, and expertise in spelling, the computer bods decided to call it V. The P/V flag is a rather overworked little beast because it does two jobs at once. The first job is to check the PARITY of the last result calculated. This means you simply count the number of 1s (or 0s) in the binary form of the last result (the binary form is always written to eight digits even if this means adding several leading zeroes). If the number of 1s is ODD then the parity is ODD. If the number of 1s is EVEN then the parity is EVEN.

The second job this flag has to do is check for an overflow. If we regard numbers from 00 to 7F as positive, and from 80 to FF as negative (as described in the section on JR) then an overflow happens if the "sign" is changed accidentally. For example 41 (positive) plus 41 (positive) equals 82 (which is negative). This is an overflow, but note this is NOT a CARRY. JP PE in this case means JUMP if there has been an overflow, and JP PO means JUMP if there has not been an overflow.

The various tests, if combined with other instructions properly, can really check for any situation conceivable. In fact thre's only one other kind of instruction you need in order to make JP and JR as powerful as IF/THEN/GOTO - that instruction is CP, or COMPARE.

CP will compare the register A with any other register, or with any constant number. It will do this by working out what would happen if that register or number were to be subtracted from A. It will not alter the value of any of the registers, but it will alter all of the FLAGS. The conditional JP and JR instructions work by checking the value of the flags. Apart from the carry flag, some of the other flags are the sign flag, which stores a one if the last calculation was negative, and a zero if the last calculation was positive; the zero flag, which stores a one if the result of the last calculation was zero, and a zero otherwise; and the parity flag, which stores a one for parity-even, and a zero for parity-odd. Although this may sound complicated you don't actually [have to] remember any of it, as long as you know how to use CP.

IF A=B THEN GOTO pq is quite easy to represent in machine code. It is CP B followed by JR Z,e. CP B will compare B with A (CP always compares with A, so that CP A is meaningless) which it does by working out A-B. The result isn't stored in any of the registers, so A and B both remain unchanged. The next instruction JR Z,e will only jump if the result A-B is zero - in other words if A equals B.

IF A<B THEN GOTO pq may be achieved in machine code in two ways. The first instruction is CP B which will compare B with A by performing A-B. Now if A is less than B then A-B will be negative, and so you could well use JP M pq, but you could also do it in another way which will allow you to use JR instead of JP, since if A is positive, and A-B is negative, then there will be a carry, and so you may use the instruction JR C e. Of course this will not work if A was "negative" (i.e. in the range 80-FF) to start with unless subtracting B caused another overflow by going through 00. This could not happen unless B was in the range 80-FF as well.

CALLING...

Even in machine code we can have subroutines. GOSUB the routine starting at address pq is CALL pq. RETURN is RET. This particular instruction should look very familiar, since it is the very same RET that we've been using to get back to BASIC at the end of the routine. This is because every USR routine is really a SUBROUTINE, even though we consider it as a program in its own right. Unfortunately there's no such thing as a CALL RELATIVE instruction, as there is with JUMP, so CALL must always be a three byte instruction. In exactly the same way as with JP we can impose IF/THEN conditions, which work in precisely the same way and are written with the same letters to define the conditions. These are:

CALL                    RET  CD        CALL pq       C9        RET  CC        CALL Z pq     C8        RET Z  C4        CALL NZ pq    C0        RET NZ  DC        CALL C pq     D8        RET C  D4        CALL NC       D0        RET NC  EC        CALL PE       E8        RET PE  E4        CALL PO       E0        RET PO  FC        CALL M        F8        RET M  F4        CALL P        F0        RET P  

As you may or may not have guessed, instructions like RET Z (return if zero) can also be used to end a machine code routine, i.e. IF RESULT 0 THEN RETurn to BASIC.

It is very important however that the value of SP is not altered during a subroutine, since the instructions CALL and RET both use the stack. CALL works by PUSHing what would have been the next address to be executed onto the stack, and RET works by POPping the first item on the stack. Thereafter both of these instructions act exactly like JP. Therefore it is possible to alter the RET address, should you need to, by POPping the first item on the stack (the previous RET address) and then PUSHing a new address. For example, to change the RET address to 20000 you could use the sequence

E1        POP HL  21204E    LD HL,20000  E5        PUSH HL  

Another useful trick is to store the value of the stack pointer somewhere at the start of a subroutine, and then retrieve it at the end. On the NEW ROM a good place to store this value is the address 16507 because neither this nor 16508 are used at all by the ROM - it is the two "spare" bytes between the system variables and the program. On the OLD ROM you don't have this spare space, but you can overwrite some of the other system variables, for example the frame counter at address 16414. The advantage of doing this is that you can PUSH and POP to your heart's content and still be sure of a safe RETurn.

At the start of a subroutine:

ED737B40  LD (16507),SP  

and at the end of a subroutine:

ED7B7B40  LD SP,(16507)  C9        RET  

EXERCISES

To make sure you have understood using the stack, and conditional jumps, write a program which will PUSH every number between one and fifty onto the stack (using PUSH AF) and then somehow manage to successfully return to BASIC. HINT: CP 32 (compare with 32 hex (50 decimal)) is quite a useful instruction here.

You'll need to know the various codes for CP. These are as follows:

BF        CP A  B8        CP B  B9        CP C  BA        CP D  BB        CP E  BC        CP H  BD        CP L  BE        CP (HL)  FEnn      CP n  

In the next chapter we'll begin loading a program which is designed to play a game of draughts. Now don't worry if this sounds rather complicated - I did say we'd begin loading it. I'm afraid you won't get the whole program until you've nearly completed the whole book, so keep a cassette handy reserved just for this program, and you can reSAVE it at each new stage. You'll need at least 4K for this.

 

 

 

Printing Things to the Screen

DRAUGHTS

In order to write a program as extensive as draughts, we'll need a faily powerful BASIC program in order to help us load it. The following is a second version of HEXLD - called HEXLD2 - which has a couple of improvements over its predecessor. One such improvement is the ability to input strings of characters such as "TO BE OR NOT TO BE" which will then be incorporated in the machine code one character at a time. To achieve this you must input ";TO BE OR NOT TO BE;" - that is, the text must be surrounded by semicolons - this is very important.

HEXLD2

OLD ROM   10 PRINT "WRITE TO ";   20 INPUT A$   30 PRINT A$   40 GO SUB 200   50 PRINT   60 LET A$=""   70 IF A$="" THEN INPUT A$   80 IF A$="S" THEN STOP   90 IF CODE(A$)=215 THEN GO TO 300  100 PRINT CHR$(CODE(A$));CHR$(CODE(TL$(A$)));"two spaces";  110 POKE X,16*CODE(A$)+CODE(TL$(A$))+36  120 LET X=X+1  130 LET A$=TL$(TL$(A$))  140 GO TO 70  200 LET X=0  210 FOR I=1 TO 4  220 LET X=16*X+CODE(A$)-28  230 LET A$=TL$(A$)  240 NEXT I  250 RETURN  300 LET A$=TL$(A$)  310 PRINT ".";CHR$(CODE(A$));"two spaces";  320 POKE X,CODE(A$)  330 IF CODE(A$)=226 THEN POKE X,118  340 LET A$=TL$(A$)  350 LET X=X+1  360 IF NOT CODE(A$)=215 THEN GO TO 310  370 LET A$=TL$(A$)  380 GO TO 70  

[Download available for 16K ZX80 -> chapter07-hexld2.o]

NEW ROM   10 PRINT "WRITE TO ";   20 INPUT A$   30 PRINT A$   40 GOSUB 200   50 PRINT   60 LET A$=""   70 IF A$="" THEN INPUT A$   80 IF A$="S" THEN STOP   90 IF CODE A$=25 THEN GOTO 300  100 PRINT A$( TO 2);"two spaces";  110 POKE X,16*CODE A$+CODE A$(2)-476  120 LET X=X+1  130 LET A$=A$(3 TO )  140 GOTO 70  200 LET X=4096*CODE A$+256*CODE A$(2)+16*CODE A$(3)+CODE A$(4)-122332  210 RETURN  300 LET A$=A$(2 TO )  310 PRINT ".";A$(1);"two spaces";  320 POKE X,CODE A$  330 IF CODE A$=216 THEN POKE X,118  340 LET A$=A$(2 TO )  350 LET X=X+1  360 IF CODE A$<>25 THEN GOTO 310  370 LET A$=A$(2 TO )  380 GOTO 70  

[Download available for 16K ZX81 -> chapter07-hexld2.p]

This program is basically the same as HEXLD except for two features. Firstly you are required to input the starting address (in hexadecimal) at which the machine code is to be loaded, and secondly it will allow you to input strings of data using their character codes, rather than hex - this is what the routine starting at 300 is for. If you input "CD0808C9" it will be interpreted as CALL 0808 followed by RET - this is exactly the same as before - however if you instead input ";LN graphic-A graphic-A TAN ;" it will mean exactly the same thing. If you compare character codes with hexadecimal by looking it up in the manual [(or appendix five)] you'll find the hex for LN is CD, hex for graphic-A is 08, and hex for TAN is C9. The semicolon is used to tell the program where the data starts and ends.

SUBROUTINES WITH DATA

Let's look at some uses for this. Perhaps the most useful subroutine we could imagine would be one which prints a string of characters to the screen. There is already a subroutine in the ROM which will print a single character. Try this program. Load it to address 4E00 (if you only have 1K you'll have to find some other suitable address).

OLD ROM   NEW ROM  CDE006              START     CALL PRPOS        <- OLD ROM ONLY  3E94      3E97                LD A,inverse-*  CD2007    CD0808              CALL PRINT  3A2540                        LD A,(S_POSN)     <- OLD ROM ONLY  3D                            DEC A             <- OLD ROM ONLY  C8                            RET Z             <- OLD ROM ONLY  18F1      18F9                JR START  

You'll discover upon running it that the screen fills up with inverse asterisks, and that it fills up very, very fast (much faster than PRINT "inverse-*"/RUN). The ROM subroutine PRINT will place the character whose code is stored in the A register at the current PRINT position on the screen. In the NEW ROM, locating the print position is automatic, but in the OLD ROM you have to call up a completely different subroutine - PRPOS (Print Position) - first, in order that the second subroutine, PRINT, knows where to place the image on the screen. PRPOS wipes out the value of the A register, but PRINT does not. Note that OLD-ROM-PRINT, and NEW-ROM-PRINT, work by two completely different methods, even though we are using them in precisely the same way, except that for the OLD ROM we have to check for end-of-screen.

It is in fact possible to put this entire program into a REM statement. NEW ROM users with only 1K [or more] might like to try clearing the machine with NEW and then typing

1 REM Y inverse-* LN graphic-A graphic-A / RAND   

(you'll need to type THEN RAND and delete the word THEN to get the word RAND in position). This is precisely the above program, but entered directly from the keyboard instead of loaded via a seperate program. Now the command RAND USR 16514 will almost instantly fill the screen! Shock - Horror - A full screen in 1K!!?

[Download available for 16K ZX81 -> chapter07-asteriskfill.p]

What we want though is a subroutine which can print any message, from "YES" to "OH WHAT A BEAUTIFUL MORNING". Suppose such a subroutine exists and it's called SPRINT (String Print). We want to be able to use an instruction something along the lines of CALL SPRINT WITH "OH WHAT A BEAUTIFUL MORNING". Here's how it will work:

CD????      CALL SPRINT  2D2A313134  DEFM "HELLO"  FF          DEFB FF  

Here DEFM means Define Message. It's not actually a machine language instruction, but it is used to specify data within a program. If you lok at the hex equivalent you'll see that 2D is hexadecimal for the character code of H, 2A for E, 31 for L and 34 for O. DEFB is also data - it means Define Byte. We could have put DEFB C9 and it would have meant the byte C9. Here we are using it to specify the end of the data to be used by SPRINT. We must ensure, however, that the machine does not try to execute these bytes, since in machine language terms they don't make a great deal of sense. Let's take a look at how we could go about writing such a subroutine as SPRINT which at the same time ensures that we don't try to execute the data (i.e. the word "HELLO" and the byte FF).

You may remember from the last chapter that CALL works by PUSHing the return address onto the stack and then jumping to the required address. RET works similarly - it POPs an address from the stack and then jumps to it. Therefore if the word "HELLO" immediately follows a CALL instruction then the address at the top of the stack will be the address of the first character of data - the "H" - we can obtain this with the single instruction POP HL. If we then increment HL by one and PUSH it back onto the stack then the effect of the next RETURN will be to jump back to the NEXT address in line - the "E". We can test for the end of the data by looking for the byte FF (which is not a printable character). Follow this subroutine through.

OLD ROM   NEW ROM  E1        E1        SPRINT    POP HL  7E        7E                  LD A,(HL)  23        23                  INC HL  E5        E5                  PUSH HL  FEFF      FEFF                CP FF  C8        C8                  RET Z  F5                            PUSH AF       <- OLD ROM ONLY  CDE006                        CALL PRPOS    <- OLD ROM ONLY  F1                            POP AF        <- OLD ROM ONLY  CD2007    CD0808              CALL PRINT  18EF      18F4                JR SPRINT  

The first four lines are designed to look at the character stored at the current return address and then increment the return address. The next two lines will only return from the subroutine if the byte FF has been found. Note that CP FF will compare A with FF, not HL which was the last thing referred to. CP will always compare A with something - in this case the hex value FF. The RET instruction (actually a RET Z or return if zero, but it works in precisely the same way) will, if you examine the listing closely enough, return you to the byte AFTER the FF, not to the FF itself. Finally, if FF has not yet been found, the subroutine PRINT will be called and the single character now in the A register will be printed to the screen. The whole routine will then be repeated over and over again until the end of the message is found.

Enter the program HEXLD2 to enable you to load machine code. Add an additional line to it - line one - which should be a REM statement with fifty arbitrary characters after the word REM. OLD ROM users must ensure that this line is never listed. LIST 9999 followed by LIST 2 will ensure this. Now RUN the program. The message WRITE TO will greet you. Input "402B" for the OLD ROM, or "4082" for the NEW ROM. This is the address in HEX, of the first character after the word REM. When prompted type in [the above machine code program appended with] the following:

OLD ROM   NEW ROM  CD2B40    CD8240               CALL SPRINT  ;OH WHAT A BEAUTIFUL MORNING;  DEFM "OH WHAT A BEAUTIFUL MORNING"  FF        FF                   DEFB FF  C9        C9                   RET  

(notice how the two bytes of the [CALL SPRINT] address have been switched around).

Now do you see the purpose of the BASIC routine in HEXLD2 which begins at line 300. Imagine how tedious it would have been to have had to type in 342D003C2D263900... and so on instead of ;OH WHAT A BEAUTIFUL MORNING; It has exactly the same effect. Now type in as a direct command RANDOMISE USR(16444) (OLD ROM) or RAND USR 16526 (NEW ROM) and what happens?

We shall use this routine to print a draughts board for us. You'll need at least 4K to load this program, but once loaded it will quite happily fit and run in 1K. If you only have 1K altogether it might be an idea to try and borrow some memory from somewhere, and then give it back only once you've got the whole of draughts in - but be warned - the listing is spread very thinly throughout the whole of the book.

If you take a look at line 330 of HEXLD2 you'll see that every time you input a double-asterisk (**) it will automatically be changed into a newline. This is a point of convenience. We can input a newline if we want, by just deleting the quote marks at the input prompt and instead typing CHR$(118), but it is far simpler, and far more convenient, to only have to type shift-H. If of course you ever need two asterisks in a row you can always type a single asterisk twice.

The next machine code program forms the very first part of DRAUGHTS. It is the routine which enables us to print the playing board. For the OLD ROM we shall begin loading this program such that the first address used is 4C04. For the NEW ROM the first address will be 4C09. NEW ROM users should remember (or write down) the sequence of BASIC commands

POKE 16389,76    [Sets RAMTOP to 4C00h (19456d)]  NEW  

which should be typed in BEFORE HEXLD2 is entered. Now enter the following machine code. WRITE TO 4C04 (OLD) or 4C09 (NEW).

OLD ROM   NEW ROM  E1        E1        SPRINT    POP HL         Increment the return  7E        7E                  LD A,(HL)      address.  23        23                  INC HL  E5        E5                  PUSH HL  FEFF      FEFF                CP FF          Return if no more text.  C8        C8                  RET Z  F5                            PUSH AF        <- OLD ROM ONLY  CDE006                        CALL PRPOS     <- OLD ROM ONLY  F1                            POP AF         <- OLD ROM ONLY  CD2007    CD0808              CALL PRINT     Print one character of  18EF      18F4                JR SPRINT      the text at a time.    CD044C    CD094C              CALL SPRINT    Print the draughts board.    001D1E1F202122232476          DEFM " 12345678"     Data for the SPRINT  1D00BC00BC00BC00BC1D76        DEFM "1 W W W W1"    subroutine.  1EBC00BC00BC00BC001E76        DEFM "2W W W W 2"  1F00BC00BC00BC00BC1F76        DEFM "3 W W W W3"  2080008000800080002076        DEFM "4        4"  2100800080008000802176        DEFM "5        5"  22A700A700A700A7002276        DEFM "6B B B B 6"  2300A700A700A700A72376        DEFM "7 B B B B7"  24A700A700A700A7002476        DEFM "8B B B B 8"  001D1E1F202122232476          DEFM " 12345678"  76                            DEFB 76  76                            DEFB 76  76                            DEFB 76  0000000000000000000000000000  DEFM "fourteen-spaces"  FF                            DEFB FF                 End of data.  C9                            RET                     Return to BASIC.  

The command RAND USR 19477 (the address of the CALL SPRINT instruction) will produce a complete draughts board picture on your screen almost instantly. Try it.

There is now one thing left to rectify - that is, we cannot as yet SAVE machine code that is stored high in memory. We shall now learn how to do so. Add the following lines:

OLD ROM                               NEW ROM  500 PRINT "4C00 TO ";                 500 PRINT "4C00 TO ";  510 INPUT A$                          510 INPUT A$  520 PRINT A$                          520 PRINT A$  530 GO SUB 200                        530 GOSUB 200  540 LET Y=(X-19454)/2                 540 LET Y=X-19456  550 DIM O(Y)                          550 DIM O$(Y)  560 FOR X=1 TO Y                      560 FOR X=1 TO Y  570 LET A=PEEK(19455+2*X)             570 LET O$(X)=CHR$ PEEK (19456+X)  573 IF A>127 THEN LET A=A-256  576 LET O(X)=PEEK(19454+2*X)+256*A  580 NEXT X                            580 NEXT X  590 SAVE                              590 SAVE "inverse-space"  600 FOR X=1 TO Y                      600 FOR X=1 TO Y  610 POKE 19454+2*X,O(X)               610 POKE 19456+X,CODE O$(X)  615 POKE 19455+2*X,O(X)/256  620 NEXT X                            620 NEXT X  630 CLEAR                             630 CLEAR  640 STOP                              640 STOP  

[Download available for 16K ZX81 -> chapter07-draughts.p]

Now, to SAVE your machine code type RUN 500. At this stage enter 4CA0. It doesn't actually matter which address you give it, so long as this address is larger than the last address of machine code (so far the last address happens to be 4C96).

The program will then SAVE automatically (line 590). Incidently if you're wondering why I've put SAVE "inverse-space" in the NEW ROM version try instead using SAVE "space" and see what happens to the line. When you LOAD the program back, OLD ROM users will need to type GO TO 600 before doing anything else. NEW ROM users won't because the program will continue automatically. Here's how the program works: An array of sufficient size to hold all the bytes to be saved is dimensioned in line 550, after which the machine code is copied into this array and SAVED. The routine at 600 does the reverse - it copies the machine code from the array up to the required address.

AND....

We leave draughts for the moment in order to introduce a few more machine language instructions which we'll need in order to continue with the program. The first of these is AND. Unfortunately for sanity the word AND doesn't mean quite the same thing as it does in BASIC. We're all used to seeing expressions like IF X=1 AND Y=1 THEN... in machine code however we use the word in a completely different context. For example AND B is a complete machine code instruction. So is AND (HL) or AND F0. In order to see how it works it is necessary to take a brief look at numbers in BINARY.

BINARY is yet another form of counting - like decimal or hex. Decimal makes use of the digits 0, 1, 2, 3, 4, 5, 6, 7, 8 and 9. Hex uses A, B, C, D, E and F as well. Binary on the [other] hand uses only the digits 0 and 1. Converting from hex to binary is very simple - much simpler than changing from decimal to hex - simply convert each digit one at a time from this table:

               HEXADECIMAL    BINARY  HEXADECIMAL    BINARY         HEXADECIMAL    BINARY  0              0000           8              1000  1              0001           9              1001  2              0010           A              1010  3              0011           B              1011  4              0100           C              1100  5              0101           D              1101  6              0110           E              1110  7              0111           F              1111  

Therefore C9 (hex) is the same as 11001001 (binary). Can you see how the binary splits up into two halves, 1100 (C) and 1001 (9)? The same is true of all numbers. What is 1E (hex) in binary? What is 01100111 in hex? Now see if you can work out what 123 (decimal) is in binary (hint: convert to hex first).

AND assignes a new value to the A register. This new value is determined by a) the previous value of the A register, and b) the value written after the word AND in the instruction. Suppose A contains 5A, and B contains 1F, and the computer then comes across the instruction AND B. Here's how the new value is calculated:

        A 01011010          B 00011111  New value 00011010  

As you can see, the digits of the new value are zero if there is a zero in the corresponding position of either or both of the old values, and a one if both the old values contained a one in that position. To make this clear just look at the columns - you'll see that in all cases two zeroes lead to a zero, two ones lead to a one, and a mixture of zeroes and ones lead to a zero. The function is called AND since a one is only obtained if A AND B have a corresponding one. It may appear to you to be rather a useless function, but it is in fact one of the most widely used machine language instructions there is. Some examples of its use are:

AND A     Leaves A unchanged, but resets the carry flag.  AND 7F    If A contains a printable character, [this] prevents it from            being inverse - both of these examples we shall make use of.  

OR....

OR is pretty similar. The rules are that two zeroes lead to a zero, two ones lead to a one. The difference here is that a mixture of zeroes and ones lead to a one rather than a zero. Instead of AND A then we could have used OR A to reset the carry flag. The function is called OR since a one is obtained if A OR B have a corresponding one. One use for the OR function could be OR 80 which, if A is a printable character, will ensure that it is an inverse character. This also we shall use. There is one other function we need to know - it is called XOR.

XOR....

XOR is not a character out of Flash Gordon, despite its sound, it is in fact short for Exclusive-OR, which is a variation on ordinary OR. Its difference is that two ones will lead to a zero. Everything else is the same as in ordinary OR, i.e. two zeroes equals zero, a mixture equals one. It follows then that XOR FF will change every single binary digit of A (this is called "complementing") from a zero to a one or vice versa. Also note that XOR A will combine A with itself and hence come up with eight zeroes. It in effect resets both A and the carry flag to zero, having the same effect as SUB A,A. This too is useful.

The reason we are interested in these functions is the manner in which we shall represent kings in our draughts game. As you have seen from the initial playing board ordinary pieces are inverse B or inverse W (for Black or White). Kings however shall be ORDINARY B or ORDINARY W. Thus a human's piece can either be 27 hex (the character code of B) or A7 (the character code of inverse B), so to check whether or not we've found one we just put it into the A register, use OR 80, and compare it with A7. This saves us from making two seperate comparisons.

 

 

A Dictionary of Machine Code

SPECIAL REGISTERS

The Z80 has two special registers which can be made use of. The first is called IX.

It is special because as well as just assigning it, as it can be used just like any other register pair with LD IX,0000 for instance, we can use it to find the contents of an address - using (IX) - just like we can with (HL). IX is different because we can add a constant to the address. Thus LD B,(IX+7B) works! If IX were 0000 then LD B,(IX+7B) will load B with the contents of memory location 007B. In no other way can we assign a single register from an address in one instruction.

There is a warning that goes with using IX though. If you are using SLOW then you must not alter the value of IX at all, otherwise you might cause a crash.

The other special register is called IY. It is used in exactly the same way as IX, except that the ROM itself gives us an added advantage. When you jump into a machine language routine, IY starts off as 4000 (hex), so that all of the system variables may be accessed directly (the system variables start off at 4000h). For example, LD L,(IY+0C) will load L with the low part of the address at which the display file begins.

Changing the value of IY will not cause a crash. It will be reset to 4000 as soon as you return to BASIC. This is done automatically by the ROM.

To find the hex value of any instruction involving IX or IY pretend you are using HL instead and look up the code for that. Then precede it by DD for IX, or FD for IY. If the IX or IY is in brackets then it must have a displacement, even if that displacement is 00 (for instance, in LD,B(IY+04) the displacement is 04). This byte should be added to the hex code, and should be the third byte of code, even if this means splitting the original code in two.

Thus if the code of LD (HL),44 is 3644, then the code of LD (IX+20),44 is DD362044. Note how the displacement 20 has been inserted into the middle of the original code in order to make it the third byte. We have now reached the stage of using four byte instruction codes. This is the longest a Z80 instruction can possibly be.

THE FLAGS REGISTER

Another special register is the FLAGS register, sometimes called the STATUS register. Usually it abbreviates itself to just F, and cohabitates with A in the hope that no-one will notice it. Its purpose is to store various bits of information about the results of calculations. Some instructions will alter all of the flags, some will alter only some of them, and some won't actually alter any flags at all. A complete list of what instruction does what is included in an appendix [four] at the back of the book.

As for the register itself: it is, like any other register, eight bits in length, but each bit has a different purpose (although two of them aren't used). These bits are each used as an individual flag which can store a value of either zero or one. The flags are, from left to right: Sign, Zero, not-used, Half-Carry, not-used, Parity/Overflow, Subtract, and Carry. The two unused flags are both more or less random, but the rest are quite specific. They work like this:

[S] The SIGN flag stores the sign (positive or negative) of the last result. A positive number resets this flag to zero, and a negative number sets it to one. For the purposes of this flag, zero is counted as positive. The value of the S flag is therefore always equal to the leftmost bit of the result. It may be tested using instructions like JP P (jump if positive) or JP M (jump if negative (minus)).

[Z] The ZERO flag checks whether or not the last result was actually zero. If so the flag is set to one, but otherwise it is reset. Watch out for this flag though - it can be very deceiving - many of the register-pair instructions simply do not change it as you'd expect: instructions like DEC or ADD for instance will only change the zero flag if applied to single registers. You are advised to check with the appendix if you are unsure.

[H] The HALF-CARRY flag is set if there is a carry from bit 3 into bit 4, or, in the case of register-pairs, from bit 11 into bit 12. It is used internally by the Z80 for such instructions as DAA, but cannot easily be tested by the programmer. It is possible to examine it using the sequence PUSH AF/POP BC/BIT 4,C and then testing the zero flag, but this is rarely done.

[P] The PARITY/OVERFLOW flag does two jobs at once. The PARITY of a result is either odd or even, depending on the number of ones in the result (when written in binary). The parity flag is assigned in exactly the opposite manner to that which you'd expect. If the parity is even, the flag is one (an odd number), and if the parity is odd, the flag is zero (an even number). The following instructions assign this flag according to the parity of the result: AND r, OR r, XOR r, RL r, RLC r, RR r, RRC r, SLA r, SRA r, SRL r, RLD, RRD, DAA, and IN r,(C). An OVERFLOW represents an "accidental" change of sign of the result - a carry from bit 6 into bit 7 effectively. The following instructions assign this flag according to whether or not we have an overflow: ADD A,r, ADC A,r, ADC HL,s, SUB A,r, SBC A,r, SBC HL,s, CP r, NEG, INC r, and DEC r.

[N] The SUBTRACT flag, also called the N flag, simply lets the machine know whether or not the last instruction was an addition, or a subtraction. You can't get hold of this flag unless you make use of PUSH and POP as I've described under HALF-CARRY, but in general you'll know what the last instruction was anyway. This flag is primarily used internally by the Z80 for instructions such as DAA.

[C] The CARRY flag you know about. It detects a carry from bit 7 into (the non-existent) bit 8, or in the case of register-pairs, from bit 15 into what would have been bit 16. It is also assigned by shift and rotate instructions, in which one bit is "lost" from a register and moves into the carry. This is probably the most frequently accessed flag of all.

ALL THE INSTRUCTIONS

By now we've seen a fair number of Z80 instructions, so you'll be wanting to expand your vocabulary of these. Here now is a detailed list of all of the instructions that are available to you. I shall cover them in alphabetical order so that you may use this chapter as a kind of dictionary of instructions. For precisely the same reason I shall re-cover the ones you've already seen. You should re-read them anyway since it will prove a useful memory aid.

ADC Starting with ADC. It comes in two forms: ADC A,r and ADC HL,s. Here we are using r to stand for either A, B, C, D, E, H, L, a numerical constant, or an address pointed to by either (HL), (IX+d) or (IY+d). s stands for one of the register pairs BC, DE, HL, SP, IX, or IY. ADC A,r is a single byte instruction. It calculates the sum A plus r plus the carry flag. The result is stored in A. ADC HL,s is a two byte instruction which evaluates HL plus s plus the carry flag, and stores the result in HL. Can you see why (ignoring the flags) ADC A,A does precisely the same job as RLA? ADC alters all of the flags.

ADD Very similar to ADC except that the carry flag is not used in the initial calculation. It is however still altered by the final result. There are one or two important differences between ADD and ADC however. Firstly the set of instructions ADD HL,s (where s means the same as it did in ADC) are one byte instructions rather than two, and secondly it is permissible to use two further sets of instructions ADD IX,s and ADD IY,s. Altering the value of IX however is not advisable if you are using SLOW. IY may be safely altered but will always be reset to 4000 (hex) on return to BASIC.

AND Only one form here - AND r. The value of the A register is altered one bit at a time. If such a bit is zero it will be unaltered. If a bit is one it will take on the value of the corresponding bit of r. Thus AND 00 is always zero, and AND FF will leave A unchanged. AND alters all of the flags - specifically the carry flag will always be reset to zero.

BIT Now this is a new one. What happens is that from time to time you'll want to know whether an individual bit of some register is one or not, but for some reason or other it becomes impractical to try and rotate or shift it into carry. BIT is specially designed to help you out here. Suppose you wanted to know the value of BIT 5 of B. The instruction is simply BIT 5,B - the result is then either zero or non-zero, which you can explot using JR Z for instance, or RET NZ. BIT does not alter the value of ANY of the registers, nor does it change the value of the carry flag. Its hex codes are listed in a table at the end of this book - it is a two-byte instruction. I tend to find it's not used very often, but when it is used it comes in very handy indeed.

CALL You've seen this one before - it's rather like GOSUB. Its exact function is as follows: PUSH the return address onto the stack, and JUMP to the call address. The return address is used by the RET instruction so it is vitally important that a subroutine should not alter the stack. You may only push things onto the stack in a subroutine if you POP them off again before you attempt to return. Call may also be used with conditions - for example CALL Z,pq (pq is an absolute address) which means CALL pq if the last calculation was zero, otherwise continue with the next instruction.

CCF Complement Carry Flag. If the carry flag was zero then change it to one. If it was one then change it to zero.

CP In the form CP r it will calculate the result of subtracting r from A, however the answer [is] NOT stored anywhere, nor is the previous value of either A or r altered. It will on the other hand alter all of the flags, so conditions like jump if zero, or jump if carry, will still work. CP r followed by JR Z will jump if A equals r.

CPD Imagine this as CP (HL), followed by DEC HL, followed by DEC BC. The zero flag is altered as if a single CP (HL) instruction had been executed. Another flag altered is the P/V flag, which works as follows: If BC decrements to zero then the P/V flag is also zero. If BC does not decrement to zero then the P/V flag is set to one. Thus JP PO will jump only if BC now equals zero. JP PE will jump only if BC is not equal to zero. The carry flag is not altered at all by this instruction.

CPDR Basically this is the same as CPD except that the instruction is executed over and over again - a kind of automatic loop. CPDR stands for Compare with Decrement and Repeat. The loop will end in one of two cases: a) if A equals (HL) - in which case the zero flag will be set, or b) if BC reaches zero - this will affect the P/V flag as in CPD. If neither of these conditions is true the instruction is re-executed.

CPI As CPD except that HL is incremented instead of decremented.

CPIR As CPDR except that HL is incremented instead of decremented.

CPL An abbreviation for complement. The register A is altered bit by bit. If any particular bit starts off as zero it is changed to one and vice versa. In other words if A starts off as 11010101 (binary) the instruction CPL will change it to 00101010 (binary). The flags are not altered, nor are any of the other registers.

DAA Suppose you wanted to add 16 (decimal) to 26 (decimal) without converting them to hex. The following seems plausible: LD A,16 then ADD A,26. Unfortunately, because the machine works in hex the final value of A will be 3C, not 42. The instruction DAA (Decimal Adjust Accumulator) will change A from 3C to 42. How it works is rather complicated - it makes a note of what's been carried where and whether you've added or subtracted and so on - but it does always work. For instance the sequence LD A,42 then SUB A,06 will again leave A with 3C, but this time round DAA will change A to 36, since 42 (decimal) minus 6 (decimal) is 36 (decimal). The instruction changes every flag appropriately.

DEC This is another one of those instructions that comes in two forms. It can be DEC r (a single register) or DEC s (a register pair). DEC r is very simple to understand - the value of the register r is decreased by one, the carry flag is unaltered, and the zero flag is changed appropriately. DEC s is the one you want to watch for, because the zero flag is NOT ALTERED! Nor are any of the other flags! Thus DEC BC followed by JR NZ,-3 is either an infinite loop or has no effect! You'll have to be very careful to remember this - a lot of my earlier programs crashed because I didn't.

DI Not a Welsh name, nor is it short for Diane or Diana. It is in fact an abbreviation (surprise! surprise!). It stands for Disable Interrupts, and although this sounds pretty confusing its use is immensely simple. An interrupt is what you get when you send little bleeps into the pins of the Z80 chip. DISABLING the interrupts means that if such a thing happens in future it is to be ignored. That's about all I can tell you I'm afraid - you'll have to consult the hardware boffs for a more detailed explanation.

DJNZ Yet another abbreviation - this time for Decrement B and Jump relative if Not Zero. So if B is 7, DJNZ will reduce it to 6. If B is zero, DJNZ will change it to FF. If B is one however, DJNZ will change it to zero, and will then jump to a new destination. The form of the instruction is DJNZ e, where e is a single byte. If B is not decremented to zero the e is ignored, if it is then e specifies how far to jump. If e is between 00 and 7F then the jump is FORWARDS, if e is between 80 and FF then the jump is BACKWARDS (with FF -1, FE -2, and so on). Start counting from the next instruction, so that DJNZ 00 is just the same as DEC B, except that DJNZ does not alter any of the flags.

EI Guess what? Another abbreviation. EI stands for Enable Interrupts, and is the opposite of DI. From now on, if the Z80 receives an interrupt, then execution of the current instruction is completed, and control then jumps to an interrupt routine. For a slightly better explanation look under IM.

EX At last - an instruction with a sensible name. EX means exchange. There are five different EX instructions - these are EX AF,AF', EX DE,HL, EX (SP),HL, EX (SP),IX and EX (SP),IY. They don't alter any of the flags. What they do is, as you'd expect, swap the values over - thus EX DE,HL replaces DE by the value HL used to contain, and HL by the value DE used to contain. The last three are rather interesting - the old value of HL (or IX or IY) is pushed onto the stack, but simultaneously the old value at the top of the stack is popped and loaded into HL. The position of the stack pointer is therefore unchanged. AF' (pronounced AF dash) is a register pair distinct from the real AF, and this is the only instruction which uses it. It is used by the SLOW hardware, so don't use EX AF,AF' while you're in SLOW.

EXX As well as AF' there are also BC', DE' and HL', which are just a set of six new registers (or three new register pairs) which can only be accessed by this one single instruction. EXX is an exchange instruction. It means exchange BC with BC' (i.e. B with B' and C with C'), DE with DE', and HL with HL' - all in the same go. This is quite safe and does not affect SLOW in the way that AF' does. It is useful for preserving the values of the registers when calling a ROM subroutine which relies upon A but wipes out the other registers, e.g. EXX/CALL ROM-SUBROUTINE/EXX. The previous values of BC, DE, and HL are now unchanged. Some of the programs later on in this book will make use of this technique.

HALT Don't be fooled by your own intuition - this isn't the same as STOP. It means do nothing, or wait forever. Once you hit a HALT instruction it will just sit there, effectively executing NOP instructions, over and over again. In fact the only way you can get out of it, once you're stuck there, is by sending the little chip an interrupt signal, so EI followed by HALT is safe since the hardware ensures that interrupts turn up pretty frequently, whereas DI followed by HALT is rather disastrous.

IM There are three forms of this instruction. These are IM 0, IM 1, and IM 2. They are there to change the Interrupt Mode (yes, another abbreviation) to either zero, one, or two. What this means is that the next time an interrupt is detected the following will happen: IF THE INTERRUPT MODE IS ZERO: The interrupt device itself must supply an instruction to be executed; IF THE INTERRUPT MODE IS ONE: The instruction RST 38 is executed; IF THE INTERRUPT MODE IS TWO: The interrupt device must supply one byte of data. This is used as the low part of an address. There is a register called I (which we so far haven't used) and the value of this register is used as the high part of an address. The machine then looks up this address and should find a second address stored there. Confusing isn't it? This second address is used as a subroutine call.

IN Short for input, but nothing like the INPUT we are used to in BASIC. It is this instruction from which Sinclair builds the LOAD routine and a keyboard scan. It has two forms - the first is IN A,(n) where n is a numerical constant. n refers to an external device - a different n for each different device. One byte of data is read from device n, and loaded into A. IN A,(n) has no effect on the flags. The second form DOES alter the flags - it is IN r,(C). The number held by the C register is used to specify the device. The number input is loaded into register r.

IND Input with Decrement. This is a deliberate digression from alphabetical order so that all of the input instructions can go together. IND can be thought of as IN (HL),(C) followed by DEC B followed by DEC HL. The carry flag is not altered, but the zero flag is altered to show whether or not B has decremented to zero.

INDR As IND but the instruction re-executes over and over again, stopping only when B reaches zero.

INI As IND except that HL is incremented instead of decremented.

INIR As INDR except that HL is incremented instead of decremented.

INC Don't Panic! At long last we're back to sensible instructions we can all understand. INC r increases the value of register r by one. Every flag except the carry flag is altered. INC s on the other hand (where s is a register-pair rather than a single register) will not change ANY of the flags. It still does the same job of course, increasing the value of register-pair s by one and zooming back round to 0000 if s starts off at FFFF, but don't use a check for zero after an INC s instruction because it simply won't work. INC HL/JR Z means jump if the instruction before INC HL came to zero, NOT if HL has reached zero. INC H/JR Z does work.

JP If you can understand GOTO 10 you can understand JP 4300. The destination is an address, not a line number, but the principle is exactly the same. JP is the machine language GOTO. We can also have conditional jumps, for example JP NZ,4300 means jump to 4300 IF NOT ZERO (in other words if the zero flag is not set). There is another form of JP which also has an analogy in BASIC - variable destinations. If you understand GOTO N you'll understand JP (HL). In this form you can't have conditions, JP NC,(HL) for instance is not allowed. Also only three registers may be used as variables - these are HL, IX, and IY. Even so these are very powerful instructions - HL can be the result of a calculation, possibly even generated at random.

JR The same as JP but slightly less powerful, and one byte shorter. Only four of the eight conditions can be used - JR Z, JR NZ, JR C, and JR NC. It is impossible to say JR PO. It is also impossible to say JR (HL). JR does not use an absolute address - the R stands for relative. You write the instruction as JR e (or JR Z,e or whatever) where the e is a single byte which specifies how far we must jump. JR 0 has no effect, and JR FE is an infinite loop, since FE represents minus two. The jump is forward if e is between 0 and 7F, and backward if e is between 80 and FF.

LD The most used instruction in the whole of machine language. All it does is to transfer data from one place to another. It has many, many forms, the simplest being LD r1,r2, that is to transfer data from one register to another. LD A,(BC) is also legal and is a one byte code, so is LD A,(DE). These are reversible, i.e. LD (BC),A and LD (DE),A are also legal. Remember that the brackets mean the contents of the address BC (or DE). Two special registers R (the memory refresh register as it's called which is used in outputting to the screen) and I (see IM) may be loaded to and from A (but only A) as in LD A,I, LD A,R, and LD R,A. The register pairs may all be loaded with either numerical constants or the contents of absolute addresses - LD s,mn or LD s,(pq). Conversely any address may be loaded with the contents of one of the register pairs - LD (pq),s. Note that register-pairs hold two bytes not one, and these are transferred to and from pq and pq+1. You can do the same with A on its own - LD A,(pq) and LD (pq),A are both allowed, but no other register can do this on its own. Finally the register pair SP - the stack pointer - may be loaded directly with either HL, IX, or IY. In other words there's a lot you can do and a lot you can't do. You can't say LD HL,SP for instance, even though LD SP,HL is allowable. Fortunately, since LD is used so very, very often it is extremely easy to become familiar with.

LDD Load with Decrement. Effectively LD (DE),(HL) followed by DEC HL, DEC DE, and DEC BC all in one go. The carry flag and zero flag are unaltered, as is the sign flag, but the P/V flag becomes zero if BC becomes zero, one otherwise, thus JP PO will jump only if BC is zero after the instruction.

LDDR As LDD, but the instruction is repeated continually until BC reaches zero.

LDI As LDD, except that DE and HL are both incremented instead of decremented.

LDIR As LDDR, except that DE and HL are both incremented instead of decremented.

NEG NEG alters the accumulator and all of the flags. As you may have gathered from the name it negates A. If A contains 1 then NEG will change it to minus one (FF). If A contains minus six (FA) then NEG will alter it to plus six (06). The same effect may be achieved using CPL followed by INC A - this alternative means of negating a number does not affect the carry flag as NEG does, but NEG is faster.

NOP This wonderous little instruction (which incidentally is short for No Operation) has a very simple purpose - its purpose is to waste time, for it does nothing at all! It's almost like a REM statement in fact, except that you can't put messages after it. It has two major uses: 1) as a delay, and 2) to overwrite previous machine coding when debugging. I'd say it was virtually indispensable.

OR In the form OR r this instruction is practically the opposite of AND r. Bit by bit, the value of the A register is changed. If a bit is one then it will be unaltered, but if it is zero it will take on the value of the corresponding bit in r. If A contains 00 then OR r is the same as LD A,r (except for the flags). If A contains FF then OR r will not change it. All of the flags are changed as you'd expect them [to be], and the carry flag is reset to zero.

OUT As with IN, OUT is nothing like the BASIC understanding of output. The instruction OUT (n),A where n is a one-byte numerical constant, will transfer the contents of A to external device n. Similarly OUT (C),r will transfer the contents of register r to the device pointed to by register C. OUT is used in the ROM to SAVE things. OUT has no effect whatsoever on the flags.

OUTD Output with Decrement. The carry flag is unchanged, but the zero flag depends on the final result of B. OUTD is equivalent to OUT (C),(HL) followed by DEC HL followed by DEC B.

OTDR A slightly different spelling in no way alters the fact that this is still an Output with Decrement and Repeat instruction - all it does is leads us to digress from alphabetical order in order to maintain consistency. Equivalent to OUTD repeated until B is zero.

OUTI As OUTD except that HL is incremented instead of decremented.

OTIR As OTDR except that HL is incremented instead of decremented.

POP Remove two bytes of data from the top of the stack and load them into a register pair. Any register pair may be used except for SP. In addition the flags register may be combined with A, allowing the instruction POP AF. Specifically, the low part of the register pair is popped first, and then the high part. The machine remembers that the stack is now two bytes shorter by altering the value of SP automatically.

PUSH PUSH s is the opposite of POP s. It stores the contents of any register pair (except SP, but including AF) at the top of the stack. It "remembers" that it has done this by altering the value of SP. The high part of s is pushed first, then the low part, so that the low part is at the top. After a PUSH instruction SP will point to the address of this low part.

RES With this instruction you can actually alter individual bits of any register. In computing circles "set" means change to one, and "reset" means change to zero, so RES is the instruction that changes the required bit to zero. For instance, to reset bit 3 of D the required instruction is RES 3,D. RES has no effect on any of the flags.

RET RET is used to return from a subroutine. It works by popping an address from the top of the stack, and then jumping to that address. It is possible to alter the address to which a subroutine will return by altering the value at the top of the stack. For example POP HL/INC HL/PUSH HL will increase the return address by one. You could for instance store one byte of data immediately after the CALL instruction, then POP HL/LD A,(HL)/INC HL/PUSH HL will store that byte in A while at the same time ensuring that the subroutine will return to the address after that data. Another trick is to push an "artificial" return address onto the stack and then JP (or JR) to a subroutine instead of calling it. Now it will "return" to wherever you want it to go! RETurn may be used with conditions if needed. It does not alter the flags.

RETI Used to end an interrupt subroutine (see IM). Its function is the same as RET, but RETI must be used instead of RET because the chip does clever things if you get a second interrupt in the middle of an interrupt subroutine! As soon as an interrupt subroutine is called a DI instruction is automatically executed, but there are such things as non-maskable interrupts, that is almighty super-high-powered interrupts that override even DI, [and] these can cause confusion if you don't use RETI.

RETN Used to end a non-maskable interrupt subroutine. Its function is the same as RETI except that the Interrupt Mode (which was altered by the non-maskable interrupt in the first place) is also restored to its previous value.

RLA An abbreviation for Rotate Left Accumulator. Each bit of A is moved one position to the left. The leftmost bit is moved into the carry, and the rightmost bit takes on the previous value of the carry. For example, if A contained 10010101 (binary) and the carry contained 0 then after a RLA instruction A will contain 00101010 and the carry will contain one. Only the carry flag is altered by this instruction.

RL On the other hand, there is another instruction which may be applied to any register. It is RL r. In fact every now and again the instruction RL A tends to disguise itself as RLA - due possibly to printing errors or bad handwriting. On the face of it they seem to do the same thing - RL means Rotate Left and its function is exactly as described in RLA. The difference however, is in what happens to the flags, for RL will alter ALL of them, RLA will only alter the carry. RL may of course be applied to any register, not just A. Incidently, RL A does precisely the same thing as ADC A,A, down to the last flag - except one - one you can't get at - called the H flag. The only way you can possibly tell the difference is by following it with a DAA instruction. ADC A,A by the way, is twice as fast.

RLCA Almost the same as RLA, but not quite. Each bit of A is moved one position to the left. The leftmost bit is moved BOTH into the carry, AND into the rightmost position of A. If, as before, A started off with 10010101 and the carry was zero, then after RLCA it will be 00101011. The carry will also be one. Only the carry flag is changed - the previous value of which is lost forever.

RLC RLC r will Rotate Left with Carry the register r in the same way that RLCA does with A. RLC A is a valid instruction, which in not the same as RLCA. RLC B is a valid instruction, but please note there is no such instruction as RLCB. The spacing is very important here. RLC r will alter all of the flags.

RLD Not to be confused with RL D, this is a COMPLETELY DIFFERENT instruction which works as follows: Write the value of A and the value of address (HL) in hex. The second hex-digit of (HL) is shifted left so that it becomes the first digit. The first digit overwrites the second digit of A. The second digit of A moves to the second digit of (HL). Thus if A contains 25 (hex) and (HL) contains EB then after an RLD has been carried out A will contain 2E and (HL) will contain B5. RLD, incidentally, stands for Rotate Left Decimal.

RRA As RLA except that the bits are moved right instead of left.

RR As RL except that the bits are moved right instead of left.

RRCA As RLCA except that the bits are moved right instead of left.

RRC As RLC except that the bits are moved right instead of left.

RRD The contents of (HL) are moved one hex-digit to the right, the rightmost digit moving into the rightmost digit of A, which in turn becomes the left digit of (HL). If A equals 25 and (HL) equals EB then after RHD A will equal 2B and (HL) will equal 5E. Note that RRD twice is the same as RLD once, and vice versa. All of the flags except carry are altered.

RST [Restart.] The same as CALL, except that it is only one byte long ALTOGETHER! It is much less powerful though for two reasons: 1) you may not use conditions. RST 0 is legal but RST NZ,0 is not. 2) only one of eight specific addresses may be called. These are 0, 8, 10, 18, 20, 28, 30, or 38. On the OLD ROM, RST 0 is the same as NEW. On the NEW ROM however RST 0 will move RAMTOP to its highest possible location, which the BASIC instruction NEW will not do. RST 0 is the same thing as pulling out the mains lead and then reconnecting it.

SBC SBC, like ADC, comes in two forms. The first is SBC A,r which will first of all subtract r from A, and will then subtract the carry digit. Similarly SBC HL,s will subtract both s and the carry flag from HL. SBC A,A is quite useful - if the carry is zero both A and the carry will end up zero - if the carry is one then A will be reassigned FF and the carry will still be one.

SET The opposite of RES. SET 4,H will change the value of bit 4 of H to one. Any bit of any register may be set.

SLA Shift Left Arithmetic. The form is SLA r. It is similar to RL r except that the rightmost bit is automatically replaced by zero. It alters all of the flags. Note that SLA A does the same thing as ADD A,A except that ADD A,A is faster.

SRA Shift Right Arithmetic. Any register may be shifted right using the format SRA r. The rightmost bit falls into the carry, but the leftmost bit remains unaltered. Thus after a SRA instruction bit 6 will always be the same as bit 7. The effect of SRA is to divide both positive and negative numbers by two: FC (minus four) becomes FE (minus two). What happens if the number is odd?

SRL Shift Right Logical. As SLA except that the bits are shifted right instead of left, and the leftmost bit becomes zero.

SUB Sometimes written as SUB r, sometimes as SUB A,r, both mean the same thing. The value of r is subtracted from the A register. Note that unlike ADD, there is no corresponding instruction SUB HL,s. If you wish to do this you must first of all reset the carry flag (usually by use of AND A) and then use SBC HL,s.

XOR XOR r alters all of the flags, resetting the carry to zero, and [affecting] the A register alone. r is not altered. What happens is that A is altered bit by bit, in the same manner as AND and OR. If a bit is zero it takes on the value of the corresponding bit of r. If on the other hand a bit is one then its new value is the complement of the appropriate bit of r. XOR A is very useful since in one byte it zeroes both the accumulator and the carry flag. Incidentally so does SUB A.

 

A Program to Help You Debug

[HEXLD3]

[Thunor: Before going any further, it is important that you read my addendum at the bottom of the page. You will also find links to downloadable versions of HEXLD3 there.]

Now we know more or less what machine language is, it's about time we learned a bit more about how to handle it. What we shall do now is to write a new program - HEXLD3 - which will allow us to do five things:

  • Input machine code.
  • Insert machine code in between previous routines, but without overwriting anything.
  • Delete machine code, closing up the gap that it occupied.
  • List machine code.
  • SAVE machine code.

The important point about this program is that the principle parts of it will themselves be in machine code, although all of the surrounding fabric will be BASIC. To work it all you need to do is enter one of the following:

  RUN        To List your stored machine code.    RUN 100    To Write new machine code.    RUN 200    To Insert new machine code.    RUN 300    To Delete previous machine code.    RUN 400    To Save machine code.  GO TO 500    To Reload saved machine code (OLD ROM only).   

More to the point - you'll need HEXLD2 in order to help you load it. The addresses used in this chapter assume that the machine code is being loaded into a REM statement in line one of a NEW ROM machine. If this is so you'll actually need 255 characters after the word REM. However, you don't have to use the same addresses as me if you don't want to. OLD ROM folk are specifically advised NOT to use a REM statement, since the machine code contains newline characters. To store machine code at different addresses to those I've used simply change the listed addresses to yours.

[APRINT]

Let's create it one part at a time. First of all a special subroutine for OLD ROM people - designed to AUTOMATICALLY print a character to the screen, in much the same way that the NEW ROM PRINT routine does. The routine will also protect all of the registers. Study this listing:

OLD ROM ONLY  E5        APRINT    PUSH HL       Store the value of HL.  D9                  EXX           Store the remaining registers.  F5                  PUSH AF       And the A register.  CDE006              CALL PRPOS    Find print-position.  F1                  POP AF        Retrieve A.  CD2007              CALL PRINT    Print character A.  D9                  EXX           Retrieve BC and DE.  E1                  POP HL        Retrieve HL.  C9                  RET           End of subroutine.  

Note that HL needs to be stacked, since CALL PRINT changes the value of HL'. The next subrotuine we'll need is a mechanism for printing to the screen the value of the A register in hexadecimal. This subroutine will INCLUDE a subroutine-call to APRINT, at least for OLD ROM people. NEW ROM people in fact already have an automatic print routine which protects all of the registers, since there is one in the ROM itself. It is not quite the same as the PRINT routine, since it also preserves the values of all the registers - this is something that CALL PRINT will not do. CALL PRINT will erase the values of B, C, D, E, H, and L. The address at which APRINT begins in the NEW ROM is 0010, and so CALL 0010 would print a character without changing any registers. This is very useful indeed.

One of the Z80 instructions designed to speed things up a bit is RST. It is in effect the same as CALL except that only one of the eight addresses may be called. It just so happens that 0010 is one of these possible addresses. RST is better than CALL for two reasons: 1) it is faster to execute, and 2) it is only one byte in length. The code for RST 10 is D7. D7 then has precisely the same effect as CD1000, that is, to print a character. OLD ROM users should note that although D7 still produces a call to 0010, it will not print a character, since in the OLD ROM there is no PRINT subroutine located at this point. RST is short for RESTART.

[HPRINT]

[Write to 4082h (16514d).]    F5        HPRINT    PUSH AF        Store A for later use.  E6F0                AND F0         This isolates the first digit.  1F                  RRA            Move the first digit to  1F                  RRA            its proper position within  1F                  RRA            the A register.  1F                  RRA  C61C                ADD A,1C       Add twenty-eight to the character                                     code, making it a hex-digit.  D7                  RST 10         Print this hex-digit.    <- NEW ROM ONLY   CD????              CALL APRINT    Print this hex-digit.    <- OLD ROM ONLY     F1                  POP AF         Retrieve the original value of A.  E60F                AND 0F         Isolate the second digit.  C61C                ADD A,1C       Add twenty-eight.    D7                  RST 10         Print it.                <- NEW ROM ONLY   CD????              CALL APRINT    Print it.                <- OLD ROM ONLY     C9                  RET  

By the way, did you understand all those ANDs and RRAs? If you didn't I'll explain exactly what's going on.

In binary, F0 is 1111 0000. This means that when you apply AND to F0 and another number, then the first four binary digits of A will be unchanged, and the second four binary digits will all become zero. Do you remember how to change from binary to hex? You have to look at it four bits at a time. The first four representing the first digit, and the second four the second digit. Thus all we have done is to change the second digit to zero.

If A were 36 then it would become 30. If it were 99 it would become 90. If it were D5 it would become D0. And so on. This is not what we want. We must shift A four bits to the right.

RRA moves A one bit to the right, replacing bit 7 (the leftmost bit) by the value of the carry. In this case the carry is zero, since we have just done an AND instruction. The new value of the carry will be the previous value of bit 0 (the rightmost bit). This will also be zero since there are now four zeroes at the right of A.

RRA then, repeated four times, will change A from 30 to 03, from 90 to 09, and from D0 to 0D. All that remains now is to add 28 (decimal) to this number and print it. We print it using the instruction RST 10.

Back to our new program. The BASIC part of the List routine will look like this:

10 PRINT "keyword-LIST"  20 GOSUB 600  30 RAND USR 16539  

To obtain the keyword LIST in line 10, either type THEN LIST (NEW ROM only) and delete the word THEN, or type the whole line as 10 LIST quote backspace backspace PRINT quote newline.

600 LET A=16533  610 PRINT "ADDRESS space";    [That's ADDRESS followed by a space.]  620 INPUT A$  630 PRINT A$    640 POKE A+1,16*CODE A$+CODE A$(2)-476            <- NEW ROM ONLY  650 POKE A,16*CODE A$(3)+CODE A$(4)-476           <- NEW ROM ONLY    640 POKE A+1,16*CODE(A$)+CODE(TL$(A$))-476        <- OLD ROM ONLY  650 POKE A,16*CODE(TL$(TL$(A$)))+CODE(TL$(TL$(    <- OLD ROM ONLY      TL$(A$))))-476    660 CLEAR  670 RETURN  

What about this USR routine at 16539 then? What will that do? And what about this business of POKEing 16533 and 16534? What's that all about? Well using my address, 16539 is the start of a routine called HLIST, which we haven't yet written. It is designed to actually LIST a machine code program in hexadecimal (hence H-List). The address 16533 is the number I've used to hold a "variable" called ADDRESS. That is to say, it is a place at which we can store a two-byte number. Any address may be used for this purpose provided that BASIC will not change that two-byte number.

This program demands four such "variables", or two-byte memory locations. They will be called BEGIN, ADDRESS, ADD2, and LIMIT. They will be used by the program as follows:

BEGIN      The address at which the subject-program begins.  ADDRESS    The address we are currently looking at.  ADD2       The address beyond which we must not progress.  LIMIT      The address at which the subject-program ends.  

I ought to explain here what is meant by "subject-program". The program we are writing is a replacement for HEXLD2. As such it is to be called HEXLD3. This is the "object-program" - the one we are writing now. But the purpose of HEXLD3 is to enable us to be able to create and examine machine code programs. The program that HEXLD3 will be used to examine is called the "subject-program". These distinctions are clearly necessary in order to avoid confusion between the two different concepts. It is of course possible to use HEXLD3 to examine itself, in which case it becomes both the object and the subject, but for the time being keep these two ideas seperate in your mind.

The addresses which I've used to store the "variables" BEGIN, ADDRESS, ADD2, and LIMIT are as follows:

Decimal  Hex     Explanation  16514    4082    The start of the subroutine HPRINT.  16531    4093    The variable BEGIN.  16533    4095    The variable ADDRESS.  16535    4097    The variable ADD2.  16537    4099    The variable LIMIT.  16539    409B    The start of the USR routine HLIST.  

Lines 640 and 650 POKE into the variable ADDRESS - giving the address at which our listing (input in hex as A$) is to begin. This idea of using part of the RAM in machine-code-area to store numbers is a very useful one. You can use it in many different programs. The numbers will be safe there even after the program ends and you are in command mode. You can type RUN or CLEAR and they won't be wiped out. They will even SAVE and reLOAD.

[HLIST]

Now for the subroutine HLIST (short for Hexadecimal List). It is a very very simple routine indeed, and should be no trouble for you to follow.

[Write to 409Bh (16539d).]    2A9940    HLIST     LD HL,(LIMIT)      Ensure that we don't progress beyond  229740              LD (ADD2),HL       the end of the subject-program.  54                  LD D,H  5D                  LD E,L  2A9540              LD HL,(ADDRESS)    Compare the current address with the  0616                LD B,16            final address.    <- OLD ROM ONLY  A7        NXTAD     AND A  ED52                SBC HL,DE  19                  ADD HL,DE  301F                JR NC,DONE                           <- NEW ROM ONLY  3027                JR NC,DONE                           <- OLD ROM ONLY  7C                  LD A,H             Print the high-part of the current  CD8240              CALL HPRINT        address in hex.  7D                  LD A,L             Print the low-part of the current  CD8240              CALL HPRINT        address in hex.  AF                  XOR A              Reset A to zero.  D7                  RST 10             Print a space.    <- NEW ROM ONLY  CD????              CALL APRINT        Print a space.    <- OLD ROM ONLY   7E                  LD A,(HL)          Print the contents of the current  CD8240              CALL HPRINT        address in hex.  CB76                BIT 6,(HL)         If this character is unprintable then  2004                JR NZ,NOPRINT      do not print it.  <- NEW ROM ONLY  2008                JR NZ,NOPRINT      do not print it.  <- OLD ROM ONLY  AF                  XOR A              Reset A to zero.  D7                  RST 10             Print a space.    <- NEW ROM ONLY  CD????              CALL APRINT        Print a space.    <- OLD ROM ONLY   7E                  LD A,(HL)          Load A with this character  D7                  RST 10             and PRINT it.     <- NEW ROM ONLY  CD????              CALL APRINT        and PRINT it.     <- OLD ROM ONLY   3E76      NOPRINT   LD A,76            Load A with a newline character.  D7                  RST 10             Print newline.    <- NEW ROM ONLY  CD????              CALL APRINT        Print newline.    <- OLD ROM ONLY   23                  INC HL             Look at the next address.  229540              LD (ADDRESS),HL    Store the current address.  18DB                JR NXTAD                             <- NEW ROM ONLY  10D3                DJNZ NXTAD                           <- OLD ROM ONLY  CF        DONE      RST 08             See below.  00                  DEFB 00  

The above program will run as listed on a NEW ROM machine. OLD ROM users should replace every RST 10 instruction by CALL APRINT as before, and are reminded that the JR byte-count must be changed accordingly at two [three] points in the program.

There are several things we can note about this program. Firstly, two new instructions have been used - BIT 6,(HL) and RST 08. Here's what they do.

BIT 6,(HL) tests the value of bit 6 of the address (HL). The result will either be 1 (if bit 6 is 1) or 0 (if bit 6 is 0). This result is not stored in any of the registers, but we can still check it with the next line JR NZ,NOPRINT, which says jump to NOPRINT if the last result (that is bit 6 of (HL)) is not zero.

Why do we need to do this? Take a look at the character set. In particular look at their character codes in hex. Notice that all of the expandable characters lie between C0 and FF (except for RND, INKEY$, and PI on the NEW ROM - these are treated slightly differently by the ROM) and that all of the characters between 40 and 7F are not printable at all (again, except for RND, INKEY$, and PI on the NEW ROM. The machine has to make a special check for these. You could argue that the NEW ROM cursor 7F was printable, but of course it looks different depending on what mode the machine is in). In fact all of the printable characters are either between 00 and 3F, or between 80 and BF, and conversely every character between 00 and 3F, or 80 and BF, is printable. What have all these in common? The fact that BIT 6 of the character code is zero. In binary thse codes run between 0000 0000 and 0011 1111, and then from 1000 0000 to 1011 1111. So all we have to do to find out whether or not a character in the set is printable, all we have to do is to look at BIT 6. The above program won't attempt to print them unless BIT 6 is zero. This is because the RST 10 routine won't expand the expandable characters, nor will it replace the others by question marks. It will crash though!

The other new instruction is RST 08. This will cause an immediate return to BASIC, stopping the program with an error code. The byte immediately after the RST 08 instruction tells it which error code to use. An error code 1 needs the data 00, since this byte has to be one less than the report code. If we wanted to be really flash we could have used 1C and got an error code of T!

Now follow the program through carefully and see what it does. Note the way we check whether or not the address ADD2 has been reached (it is stored in DE) - especially the use of AND A to reset the carry flag.

You can check that this program works by POKEing the address at which HPRINT starts into both BEGIN and ADDRESS, and by POKEing the address at which HPRINT ends into LIMIT. Then, if you type RAND USR HLIST (this is the location 16539 using my addresses) you should end up with a more or less instant listing of the subroutine HPRINT.

[Thunor: The easy way to do this is with HEXLD2 -- since you are already using this to enter the machine code subroutines above -- and writing to 4093h, entering "8240824093409340", "S" to stop, and then RAND USR 16539. Because the ADDRESS variable is modified by the machine code, you'll have to do the same again if you want to repeat the listing.]

Now if you simply type RUN and enter 4082 the program will instantly list out the start of this program. In other words we are using it to examine itself. Typing CONT or CONTINUE repeatedly will continue the listing until the end of the program is reached, when you will get a report code of 9.

[WRITE]

Now for the second part of our program, HEXLD3. The BASIC part is to look like this:

100 PRINT "WRITE"  110 GOSUB 600  120 INPUT A$  130 PRINT A$;"two spaces";  140 RAND USR 16589  150 GOTO 120  

This part calculates the length of the string A$, which because of the CLEAR statement in subroutine 600 is the first (and only) item in the variable store.

OLD ROM ONLY  2A0840    WRITE     LD HL,(VARS)  E5                  PUSH HL  06FF                LD B,FF  23        ANOTHER   INC HL  7E                  LD A,(HL)  04                  INC B  3D                  DEC A  28FA                JR Z,ANOTHER  20FA                JR NZ,ANOTHER  E1                  POP HL  CB28                SRA B  

This routine leaves the length of the string divided by two (since it needs two characters to specify one byte of machine code) in the B register and leaves HL pointing to the byte immediately before the start of the contents of the string. Notice how LD A,(HL)/INC B/DEC A/JR Z is used to check for a character 1 (a quote mark, or end of string character) as well as counting the number of characters so far (in B). Can you also see how SRA B will divide B by two?

Strings are stored differently in the NEW ROM. This actually makes things easier, not harder! Look at the corresponding NEW ROM routine which does the same job.

[Write to 40CDh (16589d).]    NEW ROM ONLY  2A1040    WRITE     LD HL,(VARS)  23                  INC HL  46                  LD B,(HL)  23                  INC HL  CB28                SRA B  

This works because the NEW ROM works by storing the length of a string immediately before the string itself. It takes two bytes for this, but notice that in both of our versions we are only using one byte for the length, so don't input more than 255 characters in one go.

Here's the rest of the [WRITE] routine:

28F4                JR Z,DONE          <- NEW ROM ONLY  28ED                JR Z,DONE          <- OLD ROM ONLY  ED5B9540            LD DE,(ADDRESS)  23        NEXTBYTE  INC HL  7E                  LD A,(HL)  87                  ADD A,A  87                  ADD A,A  87                  ADD A,A  87                  ADD A,A  23                  INC HL  86                  ADD A,(HL)  C624                ADD A,24  12                  LD (DE),A  13                  INC DE  ED539540            LD (ADDRESS),DE  E5                  PUSH HL  2A9940              LD HL,(LIMIT)  ED52                SBC HL,DE  E1                  POP HL  3004                JR NC,CHECK  ED539940            LD (LIMIT),DE  10E1      CHECK     DJNZ NEXTBYTE  C9                  RET  

You can learn several things from this routine. Firstly, notice that if you input the empty string the program will jump back to the RST 08 instruction in the previous section. This is so that you can end the program without actually having to break out.

Now look at the first few lines from CHECK onwards. What they do is this - if the end of the program (the program that WRITE is editing) is greater than the current address, do nothing, otherwise make a note of the fact that the program has got longer by altering our variable LIMIT.

[DELETING HEXLD2]

You now have two segments of machine code which, if you've typed them in properly, will work first go. Now delete the WHOLE of HEXLD2 (except of course line 1) but be very careful not to attempt to list line one. The first line now contains more characters, when the keywords in the REM are expanded, than will fit on the screen. In this circumstance the ROM will go into an infinite loop if it tries to list it - this is a design fault - the ROM should not be capable of making infinite loops. You won't be able to break out if it happens. To avoid it, type POKE 16403,10 (OLD ROM) or POKE 16419,10 (NEW ROM). Then type in lines 10 to 30, then delete the rest of the program one line at a time, lowest line number first. Now type in the rest of the program and SAVE it before you do anything else.

For NEW ROM users, it should be made clear that the REM statement will, when keywords are expanded, be longer than will fit on the screen, thus although the command LIST is acceptable (the result of which is that part of line one is listed and an error 4 message displayed), if you LIST 10, to ensure that line 10 is always at the top of the screen (sometimes this doesn't work - if not type POKE 16419,10 which always works) be warned never to delete line 10. If you do the ROM will go into an infinite loop trying to reshuffle the lines so that it can list them. In SLOW this can be quite amusing to watch, but it is always irritating because the only way you can get out of it is by pulling the plug.

[Now if you simply type RUN and enter 4082 the program will instantly list out the start of this program. In other words we are using it to examine itself. Typing CONT or CONTINUE repeatedly will continue the listing until the end of the program is reached, when you will get a report code of 9 [1].]

Now to complete the transition from HEXLD2 to HEXLD3 let's rewrite the section that will SAVE things in upper memory. The BASIC:

OLD ROM                        NEW ROM  400 DIM O(USR(ARRAY))          400 DIM O$(USR ARRAY)  410 RANDOMISE USR(STORE)       410 RAND USR STORE  420 SAVE                       420 SAVE "HEXLD3"  500 RANDOMISE USR(RETRIEVE)    500 RAND USR RETRIEVE  510 CLEAR                      510 CLEAR  520 STOP                       520 STOP  

[Thunor: OLD ROM users note that the above BASIC line 500 won't work as RETRIEVE won't reside in a line 1 REM statement, it'll be in the O array. See my BASIC lines 500 to 504 patch in appendix one.]

As you can see there are three different parts of machine code. The first, in line 400, alters nothing, but returns a numerical value to BASIC, which is then used by BASIC to reserve the correct amount of space using a DIM statement. Let's look at that part first:

Using my addresses, ARRAY is 16635, STORE is 16651, and RETRIEVE is 16669.

[ARRAY]

[Write to 40FBh (16635d).]    2A9940    ARRAY     LD HL,(LIMIT)  ED5B9340            LD DE,(BEGIN)  A7                  AND A  ED52                SBC HL,DE  229740              LD (ADD2),HL    44                  LD B,H    <- NEW ROM ONLY  4D                  LD C,L    <- NEW ROM ONLY    CB2C                SRA H     <- OLD ROM ONLY  CB1D                RR L      <- OLD ROM ONLY    C9                  RET  

The first part is obvious. The beginning address is subtracted from the end address. Again we see AND A being used to zero the carry flag so that SBC gives the right answer. Now, for OLD ROM users, this number is divided by two, because arrays use two bytes per element. For NEW ROM users we move the answer into the BC register because this is what will return to BASIC. Now for the machine code that accompanies line 410. Use RUN 100 to load it in the first place.

You may be wondering why ADD2 was loaded with the number of bytes in the code to be SAVEd. Well ADD2 is just a convenient place to store it, since it will be needed in line 410.

[STORE AND RETRIEVE]

[Write to 410Bh (16651d).]    2A1040    STORE     LD HL,(VARS)     <- NEW ROM ONLY  2A0840    STORE     LD HL,(VARS)     <- OLD ROM ONLY  110600              LD DE,0006  19                  ADD HL,DE  EB                  EX DE,HL  2A9340              LD HL,(BEGIN)  ED4B9740            LD BC,(ADD2)  EDB0                LDIR  C9                  RET  2A1040    RETRIEVE  LD HL,(VARS)     <- NEW ROM ONLY  2A0840    RETRIEVE  LD HL,(VARS)     <- OLD ROM ONLY  110600              LD DE,0006  19                  ADD HL,DE  ED5B9340            LD DE,(BEGIN)  ED4B9740            LD BC,(ADD2)  EDB0                LDIR  C9                  RET  

In case you're beginning to lose track, here's a quick round up of all the addresses we've used so far:

Decimal  Hex     Routine/Variable  16514    4082    HPRINT  16531    4093    BEGIN  16533    4095    ADDRESS  16535    4097    ADD2  16537    4099    LIMIT  16539    409B    HLIST  16589    40CD    WRITE  16635    40FB    ARRAY  16651    410B    STORE  16669    411D    RETRIEVE  16687    412F    next spare byte  

Briefly, STORE moves machine-code from upper memory and stores [it] in an array. RETRIEVE moves it back from the array to its previous position. Both of the routines start off by working out the address of the first free byte in the array. The array is the first item in the variable store, but because the OLD and NEW ROMs think differently, we have to add two to this location in the OLD ROM, and six on the NEW ROM. Can you spot the different ways in which this is done?

This is also the first time we've used the instruction LDIR. What it does is to automatically move a block of elements from address (HL) to address (DE), assuming that the number of elements contained in this block is BC. This is of course precisely what we want to do. LDIR does alter the value of each of the register pairs BC, DE, and HL, but that doesn't concern us since the next thing we do is RET.

LDIR is very, very useful indeed, but you must remember which way round it goes. It loads from (HL) into (DE). Have you ever pressed 'record' instead of 'play' when trying to load programs from tape? Well that's exactly what will happen to your machine code if you get DE and HL the wrong way round for LDIR - it will just be wiped out - and there's no going back.

As long as you can see exactly what's happening you're OK. If you can't then get a piece of paper and write down the values of each register at each stage. Work through until you're convinced you know exactly what's happening all the way through.

[INSERT]

We now have a BASIC program called HEXLD3 which contains a fair number of machine code subroutines. As it stands it will both LIST and WRITE machine code, and can also be used to SAVE any machine code or data which is stored in spare RAM space high in memory. This is all that HEXLD2 did. You have now the ability to enter your own machine code programs very easily, but what you can't yet do is edit them if you make a mistake. That is what the next section is for - it is called INSERT, and will insert whatever you input between the surrounding code, without overwriting it. The BASIC part of the routine is this:

200 PRINT "INSERT"  210 GOSUB 600  220 INPUT A$  230 PRINT A$;"two spaces";  240 RAND USR 16687  250 GOTO 220  

And the machine code which goes with it (which NEW ROM users should write to address 16687) is as follows:

                                    [Write to 412Fh (16687d).]    OLD ROM                             NEW ROM  2A0840    INSERT    LD HL,(VARS)    2A1040    INSERT    LD HL,(VARS)  E5                  PUSH HL         23                  INC HL  01FFFF              LD BC,FFFF      4E                  LD C,(HL)  23        MORE      INC HL          23                  INC HL  7E                  LD A,(HL)       46                  LD B,(HL)  03                  INC BC  3D                  DEC A  28FA                JR Z,MORE  20FA                JR NZ,MORE  E1                  POP HL  
[Both ROMs Continued]  CB28      COPYUP    SRA B  CB19                RR C  2002                JR NZ,NOTEMPTY  CF                  RST 08  08                  DEFB 08  C5        NOTEMPTY  PUSH BC  2A9940              LD HL,(LIMIT)  ED5B9540            LD DE,(ADDRESS)  A7                  AND A  ED52                SBC HL,DE  23                  INC HL  44                  LD B,H  4D                  LD C,L  E1                  POP HL  ED5B9940            LD DE,(LIMIT)  19                  ADD HL,DE  229940              LD (LIMIT),HL  EB                  EX DE,HL  EDB8                LDDR  CDCD40              CALL WRITE  C9                  RET  

Now exactly how this works is quite complicated, so think carefully. The part between INSERT and COPYUP finds the length of the string A$. As you can see it required a completely different method for each ROM. See WRITE on this, since it is very similar here.

Between COPYUP and NOTEMPTY the length of the string is divided by two, and if it is zero returns to BASIC with error code 9. This is the job of the RST 08/DEFB 08 sequence. From then on we are concerned with moving part of the program being edited. Look at the diagram below.

       ------+--------------+--------------+----------------  BEFORE:      |              |              |          -----+--------------+--------------+----------               |              |              |             begin         address         limit           ------+--------------+----------+--------------+-----  AFTER:       |              |\/new/\/\/|              |          -----+--------------+----------+--------------+-----               |                         |              |             begin                    address         limit  

As you can see, we need to load a complete block of elements from one point to another, but unlike before the new and old positions overlap. This is a slight problem, and we have to be very careful how we load it. If we were to simply assign HL to ADDRESS(before) and DE with ADDRESS(after), and then use LDIR as before (having assigned BC to the number of elements in the block first) then since LDIR moves things one byte at a time the first few elements would end up in the middle of the block, only to be copied up for a second time. The program would be completely corrupted.

We can get round this flaw by sneaking up on the problem sideways while it's not looking. What we do is we block load it from the other end! This means loading HL with LIMIT(before) and DE with LIMIT(after) and use [using] LDDR instead of LDIR.

Having found the length of the new section, this length is pushed onto the stack. BC is then loaded with the length of the block to be moved. See how this is worked out. Then HL and DE are correctly assigned, making use of the fact that the length of the new section is at the top of the stack, and the new limit is stored in our "variable" LIMIT.

After the block load is successfully carried out we call the WRITE subroutine to fill the shaded area in the diagram with the contents of the input string. This will work because the above program does not change the value of the variable ADDRESS. WRITE will simply overwrite the shaded region, moving the current address pointer to its new position. We then return to BASIC for the next input.

To test the program, use WRITE to write "9D9E9FA0A1A2A3A4A5" to the point just beyond where our program currently ends. This will list as inverse-123456789. Now use INSERT. Give it the address of the inverse five, and input "00"/"201E"/"00". Here / means newline. When you list it you'll find four new characters have been inserted. Notice that the routine allows you to input as many characters as you like in one go, and that it allows you to press newline as many times as you like. Newline on its own (i.e. inputting the empty string) will break out of the program.

[DELETE]

The final section to add to our program is DELETE. This will look (in BASIC) like this:

300 PRINT "DELETE"  310 GOSUB 600  320 LET A=16535  330 GOSUB 610  340 RUN USR 16732  340 RAND USR 16732  

The first four lines load the initial and final addresses into the variables ADDRESS and ADD2. Line five calls the machine language routine that will do the task for us.

Here's what the machine code has to do. Look at the diagram below. Here the shaded region must be removed.

       ------+--------------+----------+--------------+-----  BEFORE:      |              |//////////|              |          -----+--------------+----------+--------------+-----               |              |          |              |             begin         address      add2          limit           ------+--------------+--------------+----------------  AFTER:       |              |              |          -----+--------------+--------------+----------               |              |              |             begin         address         limit  

This is quite simple - we just use LDIR quite straightforwardly. You might think there would be some effort involved in calculating the new limit, but not so. LDIR alters the value of HL and DE for us in quite an advantageous way - as we shall see.

[Write to 415Ch (16732d).]    2A9940    DELETE    LD HL,(LIMIT)  ED5B9740            LD DE,(ADD2)  D5                  PUSH DE  A7                  AND A  ED52                SBC HL,DE  44                  LD B,H  4D                  LD C,L  E1                  POP HL  23                  INC HL  ED5B9540            LD DE,(ADDRESS)  EDB0                LDIR  1B                  DEC DE  ED539940            LD (LIMIT),DE  CF                  RST 08  08                  DEFB 08  

As LDIR moves from one end of the blocks being shifted to the other, HL and DE move with it, so HL ends up to the right of the original block, and DE ends up to the right of the copy. Thus a simple DEC DE after the LDIR will set it to exactly the right place for our new limit. Load this routine to address 410D (OLD ROM) / 415A [415C] (NEW ROM), using INSERT. You should now have one or two spare characters after the end of the program. Use DELETE to wipe them out - this will of course test whether or not you have typed in DELETE correctly.

Now SAVE this program permanently. This is the final version. All you have to do in order to use it in future is to type RUN 100 and enter the address of the variable BEGIN (403C or 4093). Then input the address to which the program you are about to write will begin, then simply newline on its own. RUN 100 a second time to actually begin inputting a program.

[Download available for 16K ZX81 -> chapter09-hexld3.p]

[Download available for 16K ZX80 -> chapter09-hexld3.o]

[ADDENDUM]

[Thunor: Having worked through this chapter and written HEXLD3 for both ROMs, I can help to clear up some confusions.

  • Don't type in the BASIC parts until you've reached the section Deleting HEXLD3 as they conflict with HEXLD2s.
  • Many of the OLD ROM differences were not supplied, and so I've included them and adjusted their relative jumps, so if the author says that you should do these things then you shouldn't because I've already done them.
  • The author says for the OLD ROM you can't use a line 1 REM statement to store HEXLD3 which is absolutely correct, but she doesn't suggest where you should be writing to, and assumes that you are writing there anyway in listings as she expects you to go through HEXLD3 and change all the addresses when you've decided what they will be.
  • To decide where to write the OLD ROM machine code, a little peek at chapter 11 states that the variable BEGIN is located at 4A3Ch and that it should contain a value of 4A00h, therefore APRINT must be written to 4A1Ah.
  • There is an issue with the RETRIEVE machine code subroutine, and also the way it is executed from BASIC for users wishing to write HEXLD3 for the OLD ROM, and for future NEW ROM HEXLD3 users. Because it was written for the NEW ROM it references absolute addresses BEGIN and ADD2 within the line 1 REM statement which is where HEXLD3's machine code (and RETRIEVE itself) is stored, but if HEXLD3's machine code isn't stored within a line 1 REM statement -- such as with the OLD ROM version -- then this isn't going to work. If HEXLD3 is stored in the O array and restored to memory after loading, then BEGIN and ADD2 should be read from the O array and RETRIEVE itself should be executed from inside the O array. Therefore if you are intending to work through this chapter and write HEXLD3 for the OLD ROM then when you get to write BASIC line 500 you should replace it with my 500 to 504 patch from appendix one. NEW ROM users needn't worry whilst HEXLD's machine code is stored in the line 1 REM statement, but in chapter 11 NEW ROM users will be required to relocate the machine code to high memory and delete line 1, and although the author offers a BASIC modification to executing RETRIEVE from within the O$ array, she doesn't appear to realise that the RETRIEVE machine code itself will still be attempting to read BEGIN and ADD2 from memory addresses outside of the O array. The NEW ROM's equivalent solution to this problem is shown towards the beginning of chapter 11.
  • A third of the way through this chapter, the author gives you the opportunity to check that the program works by using it to list itself. I have added a note suggesting using HEXLD2 for this -- the program you are writing HEXLD3 with -- and I recommend you attempt this test because it initialises the BEGIN, ADDRESS, ADD2 and LIMIT variables with values that are required for HEXLD3 to function. If you skip this test and get to the part where you start deleting HEXLD2, then you will inevitably be forced to initialise these variables (memory locations) with BASIC POKEs as HEXLD3 won't work at all until at least BEGIN and LIMIT are set-up.
  • There are detailed program listings and instructions for both ROMs in appendix one.

Additional content end.]

 

 

Scanning the Keyboard

Now it's time to explore how we can make use of some of the other subroutines that are remarkably well-hidden within the ROM. Specifically we'll cover two of these subroutines, which between them will enable us to scan the keyboard and locate which, if any, of the keys on the keyboard are being depressed. On the NEW ROM we can make use of these subroutines just by calling them, but we can't on the OLD ROM because they're simply not there. For the benefit of the people with OLD ROMs I shall include a section at the end of this chapter explaining how these programs may be made to work by actually inputting these subroutines yourselves. This section will also be of interest to those of you with NEW ROMs, since it will give you an insight into how the subroutines actually work.

The first such subroutine is an amazing little keyboard scan, which begins at address 02BB. It may be accessed simply by calling that address, i.e. CALL KSCAN, or CDBB02 in hex. It doesn't actually produce a very usable answer though. Let's see exactly what it does do.

It returns a value to the HL register pair. Actually it returns seperate and independent values - one to H and one to L. Here's how the value of L is interpreted:

Imagine the keyboard (excluding SHIFT) divided up into eight horizontal sections, each containing five keys (except for section zero which only contains four, because SHIFT is ommitted). Notice how each section has a corresponding number between zero and seven. Now, if there is no key depressed then L will return a value FF. However, if one or more keys are depressed, then the appropriate BIT (of L) will be reset to zero. In other words, if you are pressing Q, W, E, R, or T then bit 2 will be reset - if you are pressing B, N, M, full-stop, or space, then bit 7 will be reset. This means that L can return the following:

                                   BINARY    HEX  If no key is depressed             11111111  FF  If a section 0 key is depressed    11111110  FE  If a section 1 key is depressed    11111101  FD  If a section 2 key is depressed    11111011  FB  If a section 3 key is depressed    11110111  F7  If a section 4 key is depressed    11101111  EF  If a section 5 key is depressed    11011111  DF  If a section 6 key is depressed    10111111  BF  If a section 7 key is depressed    01111111  7F  
+------------------------------+------------------------------+  | SECTION 3                    |                    SECTION 4 |  | ( 1 ) ( 2 ) ( 3 ) ( 4 ) ( 5 )|( 6 ) ( 7 ) ( 8 ) ( 9 ) ( 0 ) |  +-+----------------------------+-+----------------------------+-+    | SECTION 2                    |                    SECTION 5 |    | ( Q ) ( W ) ( E ) ( R ) ( T )|( Y ) ( U ) ( I ) ( O ) ( P ) |    +-+----------------------------+-+----------------------------+-+      | SECTION 1                    |                    SECTION 6 |      | ( A ) ( S ) ( D ) ( F ) ( G )|( H ) ( J ) ( K ) ( L ) (ENT) |      +--+-----------------------+---+--------------------------+---+         | SECTION 0             |                    SECTION 7 |    (SHF)|( Z ) ( X ) ( C ) ( V )|( B ) ( N ) ( M ) ( . ) (   ) |         +-----------------------+------------------------------+  

As an exercise see if you could work out what L would be if both S and P were depressed at the same time.

The value returned by H is determined by a similar principle, but notice how the keyboard is divided up here - not horizontally but vertically. Notice also that the SHIFT key has a section all to itself - section 0. Now if you press key S for instance then H will return FB (in binary 11111011). We have already seen that L would give FB as well, so that HL returns FBFB. Can you see why it is impossible for this value to be obtained from any other key?

   1     2     3     4     5     5     4     3     2     1  +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+   \     \     \     \     \     \     \     \     \     \     \    \( 1 )\( 2 )\( 3 )\( 4 )\( 5 )\( 6 )\( 7 )\( 8 )\( 9 )\( 0 )\     \     \     \     \     \     \     \     \     \     \     \      \     \     \     \     \     \     \     \     \     \     \       \( Q )\( W )\( E )\( R )\( T )\( Y )\( U )\( I )\( O )\( P )\        \     \     \     \     \     \     \     \     \     \     \         \     \     \     \     \     \     \     \     \     \     \          \( A )\( S )\( D )\( F )\( G )\( H )\( J )\( K )\( L )\(ENT)\           +-----+     +     +     +     +     +     +     +     +     +     +-----+    /     /     /     /     /     /     /     /     /     /     |(SHF)|   /( Z )/( X )/( C )/( V )/( B )/( N )/( M )/( . )/(   )/     +-----+  +-----+-----+-----+-----+-----+-----+-----+-----+-----+        0        2     3     4     5     5     4     3     2     1          N        N     N     N     N     N     N     N     N     N        O        O     O     O     O     O     O     O     O     O        I        I     I     I     I     I     I     I     I     I        T        T     T     T     T     T     T     T     T     T        C        C     C     C     C     C     C     C     C     C        E        E     E     E     E     E     E     E     E     E        S        S     S     S     S     S     S     S     S     S                       

Now let's see what would happen if you pressed SHIFT S. Both bits zero and two would be reset giving, in binary, 11111010. In hex this is FA, so HL would return as FAFB - which is different to the value produced without shift. We can see the precise effect of SHIFT from the table:

                                   WITHOUT SHIFT   WITH SHIFT                                     BINARY    HEX   BINARY    HEX  If no key is depressed             11111111  FF    11111110  FE  If a section 1 key is depressed    11111101  FD    11111100  FC  If a section 2 key is depressed    11111011  FB    11111010  FA  If a section 3 key is depressed    11110111  F7    11110110  F6  If a section 4 key is depressed    11101111  EF    11101110  EE  If a section 5 key is depressed    11011111  DF    11011110  DE  

It should by now be reasonably clear how each individual key, with or without shift, produces its own unique code in the HL register pair. If two keys are both in the same horizontal section they cannot possibly both be in the same vertical section. Note that SHIFT on its own returns a value of FEFF which is not the same as no key depression at all.

The subroutine which I've called KSCAN does have one big disadvantage though - it will completely wipe out the previous values of all the registers! If you want to preserve them you'll have to make use of the stack as follows:

F5                  PUSH AF  C5                  PUSH BC  D5                  PUSH DE  CDBB02              CALL KSCAN  D1                  POP DE  C1                  POP BC  F1                  POP AF  

Now we want to turn these rather obscure numbers into real character codes. It just so happens that all of these codes are rather cleverly stored in the ROM beginning at address 007E. By "rather cleverly" I mean in a most convenient order, as follows: First the straightforward characters:

007E    ZXCV ASDFG QWERT 12345 09876 POIUY newline LKJH space .MNB  

(There are no spaces between the characters - they are printed here to make the ordering more obvious.) Then the shift characters:

00A5    :;?/ STOP LPRINT SLOW FAST LLIST "" OR STEP <= <> EDIT AND THEN          TO cursor-left RUBOUT GRAPHICS cursor-right cursor-up cursor-down          ")($ >= FUNCTION =+- ** £,><*  

Can you see how the ordering relates to which sections the key lies in? We could quite easily write a subroutine now to convert from the strange number we already have in HL to an address between 007E and 00CB (the last item in the table - the *) but it turns out that we don't need to because that nice man Uncle Clive has already done it for us with a subroutine which I shall call FINDCHR beginning at 07BD. The ROM people probably have their own name for it but they keep it shrouded in mystery. The subroutine performs the following task - given a value as defined above, in the BC register, it will work out the address at which the appropriate character is stored - the final result ending up in HL.

It does have a problem though. If you're not pressing a key then surely you shouldn't end up with a character to print! You'll have to prevent this yourself. One way would be as follows. Notice though how we move the result from the first subroutine into the BC register before calling the second.

CDBB02    START     CALL KSCAN  44                  LD B,H  4D                  LD C,L  51                  LD D,C  14                  INC D  3E00                LD A,00  2804                JR Z,NOCHR  CDBD07              CALL FINDCHR  7E                  LD A,(HL)  ...       NOCHR     ...                      rest of program  

There are several things to note about this example. Firstly that two seperate instructions, LD B,H and LD C,L are needed to transfer HL to BC since there is no single instruction LD BC,HL. Secondly that the condition JR Z means if D is zero, not A - LD does not alter any of the flags. If D is zero after being incremented then it must have been FF beforehand, which means that L must have been FF after it came out of the first subroutine. This is the check that a key is being pressed. A is loaded with zero and if no key is pressed it remains zero, otherwise it takes on the code of whichever character you're touching on the keyboard.

There is here a slight ambiguity in that the zero is also produced if you press space. You could use LD A,01 instead of LD A,00 since the character whose code is one (character-01) is not available from the keyboard. Now there is no ambiguity since zero means space and one means no character is being pressed. If you have SLOW at your disposal you could omit LD A,00 altogether and use JR Z,START instead of JR Z,NOCHR. Now the program will WAIT until a key is pressed before continuing. Without SLOW it will still wait but you'll have to suffer a blank screen in the meantime.

The A register now contains a value corresponding precisely to the function INKEY$. In this way real time games are just as feasible in machine code as they are in BASIC.

Another interesting part of the ROM is the very last bit - the half of a K that runs from 1E00 to 1FFF. It's not a subroutine, it's a table - a very long table - actually the longest table in ROM. It stores the dot pattern of every symbol used by the computer - that is all of the printable characters. It takes eight bytes to store a single character symbol, so for example, the characters A, B and C are represented, in binary, by:

0 0 0 0 0 0 0 0    0 0 0 0 0 0 0 0    0 0 0 0 0 0 0 0  0 0 1 1 1 1 0 0    0 1 1 1 1 1 0 0    0 0 1 1 1 1 0 0  0 1 0 0 0 0 1 0    0 1 0 0 0 0 1 0    0 1 0 0 0 0 1 0  0 1 0 0 0 0 1 0    0 1 1 1 1 1 0 0    0 1 0 0 0 0 0 0  0 1 1 1 1 1 1 0    0 1 0 0 0 0 1 0    0 1 0 0 0 0 0 0  0 1 0 0 0 0 1 0    0 1 0 0 0 0 1 0    0 1 0 0 0 0 1 0  0 1 0 0 0 0 1 0    0 1 1 1 1 1 0 0    0 0 1 1 1 1 0 0  0 0 0 0 0 0 0 0    0 0 0 0 0 0 0 0    0 0 0 0 0 0 0 0  

Can you pick the letters A, B and C from the digits above? The pattern is held by the positions of the "ones" amongst the "zeroes". When they finally appear on your TV screen they look like this:

+-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  | | | | | | | | |  | | | | | | | | |  | | | | | | | | |  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  | | |@|@|@|@| | |  | |@|@|@|@|@| | |  | | |@|@|@|@| | |  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  | |@| | | | |@| |  | |@| | | | |@| |  | |@| | | | |@| |  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  | |@| | | | |@| |  | |@|@|@|@|@| | |  | |@| | | | | | |  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  | |@|@|@|@|@|@| |  | |@| | | | |@| |  | |@| | | | | | |  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  | |@| | | | |@| |  | |@| | | | |@| |  | |@| | | | |@| |  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  | |@| | | | |@| |  | |@|@|@|@|@| | |  | | |@|@|@|@| | |  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  | | | | | | | | |  | | | | | | | | |  | | | | | | | | |  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  

Suppose we now wished to reconstruct these letters in an enlarged form - using a pixel (quarter-square) for each dot. This means that each character we print should be a graphics character (space and inverse-space both count as graphics characters) chosen so that the correct quarters are black.

There are two ways of doing this. One is to make use of the NEW ROM character codes, in which the graphics are arranged in a very clever order - unfortunately we would not be able to adapt this system to the OLD ROM. The second is to include sixteen bytes of data within our program representing the graphics symbols in any order we care to choose. Let's take a look at the first method first.

Suppose the bottom right corner is WHITE. If we give the other pixels numbers 1, 2 and 4 then simply adding them up gives the required character code. You can check this by comparing the diagram below with the character set in the Sinclair manual.

If the bottom right hand corner is BLACK then we need to give the other pixels the numbers -1, -2, and -4. To work out the code of any graphics symbol here we start off with the number 135 (decimal) and subtract appropriately the required number for each black pixel. Again you can check this by comparing the diagram below with the Sinclair manual.

Incidently it is worth pointing out here that many copies of the Sinclair manual incorrectly give the character of 135 as character-07. This is a misprint - it should of course be character-87. Try typing PRINT CHR$ 135 to check. Character seven is character-07 - the manual gives this correctly.

+---------+---------+    +---------+---------+  |         |         |    |         |         |  |    1    |    2    |    |   -1    |   -2    |  |         |         |    |         |         |  |         |         |    |         |         |  +---------+---------+    +---------+---------+  |         |         |    |         |/////////|  |    4    |  white  |    |   -4    |//black//|  |         |         |    |         |/////////|  |         |         |    |         |/////////|  +---------+---------+    +---------+---------+  

[Thunor: The author is saying that if the bottom-right is white then you add-up the values in the other squares if they are black, and if the bottom-right is black then you subtract the values in the other squares from 135 if they are black. A quick peek at the NEW ROM character set in appendix five illustrates this.]

The character codes of the OLD ROM graphics symbols are unfortunately rather random, so there is no single system for working out the code, given which pixels should be black and which should be white. In order that the program to follow should work on both ROMs we shall adopt a slightly different method. Instead of distinguishing two different cases (that is the colour of the bottom right-hand pixel) we shall treat every quarter-square the same, and code them as follows:

+---------+---------+  |         |         |  |    8    |    4    |  |         |         |  |         |         |  +---------+---------+  |         |         |  |    2    |    1    |  |         |         |  |         |         |  +---------+---------+  

We should then have to include in our program a DATA section which lists the graphics symbols in the order space graphic-34628TQ1Y5W7RE inverse-space

Move RAMTOP to address 4380 (this is a hex address) by typing POKE 16388,128 POKE 16389,67 then NEW. Now load the following program to address 4380 (in decimal this is 17280, meaning 1K users will still be able to run it). As it stands this program is best run in SLOW. We shall see how to alter it so that it will run in FAST later.

[Thunor: Unless you really want this to run in 1K I suggest you don't set RAMTOP this low because you're going to want to use HEXLD2 or HEXLD3 to type it in and SAVE it. I recommend HEXLD3 -- seeing as you've just written it or downloaded mine -- and writing to 4D00h (19712d), therefore the instruction 218043 LD HL,DATA below should read 21004D LD HL,DATA, and to start it type RAND USR 19728. NEW ROM users don't forget that HEXLD3 requires initialising for a new program.]

[NEW ROM ONLY]  00870483  DATA      DEFM "space graphic-346........"    This is the table of  02850681            DEFM "graphic-28TQ............."    graphics symbols in  01860582            DEFM "graphic-1Y5W............."    the required order.  03840780            DEFM "graphic-7RE inverse-space"      [OLD ROM ONLY]  00070603  DATA      DEFM "space graphic-346........"    This is the table of  05820884            DEFM "graphic-28TQ............."    graphics symbols in  04880285            DEFM "graphic-1Y5W............."    the required order.  83868780            DEFM "graphic-7RE inverse-space"    [Both ROMs Continued]  CDBB02    START     CALL KSCAN         Wait for human to take  2C                  INC L              finger off of key.  20FA                JR NZ,START  CDBB02    WAIT      CALL KSCAN         Wait for new key to be  44                  LD B,H             pressed.  4D                  LD C,L  51                  LD D,C  14                  INC D  28F7                JR Z,WAIT  CDBD07              CALL FINDCHR       Locate appropriate  7E                  LD A,(HL)          character code.  A7                  AND A  17                  RLA                Multiply by eight, but  17                  RLA                return to BASIC if a non-  D8                  RET C              printable character is  17                  RLA                pressed.  1600                LD D,00  CB12                RL D  5F                  LD E,A  21001E              LD HL,CTABLE       Find start of dot  <- NEW ROM ONLY  21000E              LD HL,CTABLE       Find start of dot  <- OLD ROM ONLY  19                  ADD HL,DE          pattern.  0E04                LD C,04  0604      OUTERLOOP LD B,04  56                  LD D,(HL)          Transfer two lines of dots  23                  INC HL             into D and E  5E                  LD E,(HL)  23                  INC HL  E5                  PUSH HL  AF        INNERLOOP XOR A              Compute which graphics  CB12                RL D               character is to be  17                  RLA                printed.  CB12                RL D  17                  RLA  CB13                RL E  17                  RLA  CB13                RL E  17                  RLA  218043              LD HL,DATA         Get this character from  85                  ADD A,L            the table of graphics  6F                  LD L,A             symbols.  7E                  LD A,(HL)  D9                  EXX                Print this symbol.  F5                  PUSH AF            <- OLD ROM ONLY  CDE006              CALL PRPOS         <- OLD ROM ONLY  F1                  POP AF             <- OLD ROM ONLY  CD2007              CALL PRINT         <- OLD ROM ONLY  CD0808              CALL PRINT         <- NEW ROM ONLY  D9                  EXX  10E6                DJNZ INNERLOOP     Next print    <- NEW ROM ONLY  10E1                DJNZ INNERLOOP     Next print    <- OLD ROM ONLY  E1                  POP HL             position.  3E76                LD A,76            End of current line.  D9                  EXX  F5                  PUSH AF            <- OLD ROM ONLY  CDE006              CALL PRPOS         <- OLD ROM ONLY  F1                  POP AF             <- OLD ROM ONLY  CD2007              CALL PRINT         <- OLD ROM ONLY  CD0808              CALL PRINT         <- NEW ROM ONLY  D9                  EXX  0D                  DEC C  20D4                JR NZ OUTERLOOP    Next line begins.  <- NEW ROM ONLY  20CA                JR NZ OUTERLOOP    Next line begins.  <- OLD ROM ONLY  18AF                JR START           Start again.       <- NEW ROM ONLY  C9                  RET                                   <- OLD ROM ONLY  

The program is now complete. Make sure you are in SLOW mode and start the program off by typing RAND USR 17296. DO NOT type RAND USR 17280 since this is purely data and will not run. You should see a completely blank screen. Press "C", and watch what happens. Now press "A". Interesting isn't it? Try experimenting with different keys to see what happens - and what happens when you run out of screen?

[Download available for 16K ZX81 -> chapter10-enlarge.p]

You may have been confused by the use of the instruction EXX which was used four times in the above program. Its function is very easy to explain. As you know, the registers B, C, D, E, H, L, and A can [be] very easily manipulated, but there are also seven other registers, called B', C', D', E', H', L', and A' (pronounced A-dash or A-prime). These are not so easy to manipulate and can in practice only be used for storage purposes. The instruction EXX means exchange B and B', C and C', D and D', E and E', H and H', L and L'. Thus all the registers except A lose their previous values but take on the values of their alternative registers. Likewise the alternative registers take on the original values of the usual registers.

The reason we need to do this is because the ROM subroutine PRINT destroys the previous values of BC, DE, and HL. We could have preserved them by pushing them onto the stack, but EXX works just as well here and is only one instruction.

Lets take a closer look at the above program and sort out exactly what each bit does. First of all we find the right character code, which gets stored in the A register. The instruction AND A resets the carry flag to zero. RLA will then multiply A by two. Now we know that this character is on the keyboard and can be obtained in one touch, so it is not an inverse character. Rotating left then will move the leftmost bit, which must be a zero, into the carry flag. RLA a second time will again multiply by two (since we know the carry is zero), however, if the character is NOT PRINTABLE (such as newline or STOP) then bit 6 of the original value will be a one. This will now be moved into carry. The instruction RET C ensures that if this circumstance ever occurs the program will terminate.

Knowing then that the carry is still zero we can use RLA once more to multiply by two. Here however, bit 5, which [is] a necessary part of the character code, will be moved into the carry flag. To move the carry digit into D we use two instructions LD D,00 and RL D. D will then contain either zero or one. LD E,A ensures that register-pair DE now contains eight times the original value of A.

The other interesting part is the first nine lines of the INNER-LOOP. A is loaded with the first two bits of D and the first two bits of E. This gives a number between zero and fifteen which corresponds to the required graphics symbol. It is NOT the character code, it is the specially designed code we worked out earlier on. Notice how the NEXT bits of D and E are now automatically in place at the extreme left.

For those of you who do not have SLOW I suggest replacing the last instruction, JR START by RET. You could then have a surrounding BASIC program as follows:

10 RAND USR 17296  20 PAUSE 40000       <- NEW ROM ONLY  20 INPUT A$          <- OLD ROM ONLY  30 RUN  

THE SUBROUTINES

OLD ROM users will by now be feeling quite envious at NEW ROM people for having these subroutines at their disposal. Of course there is a keyboard scan in the OLD ROM, but it isn't a subroutine - i.e. it doesn't end in RET. One call to it and you're stuck there forever! What we'll have to do is rewrite them ourselves. We can do this by taking all of the important bits from the subroutines in the NEW ROM.

First of all KSCAN. This is the required subroutine. Don't worry if there are parts of it you don't understand - all will become clear in due course.

21FFFF    KSCAN     LD HL,FFFF  01FEFE              LD BC,FEFE  ED78                IN A,(C)  F601                OR 01  F6E0      LOOP      OR E0  57                  LD D,A  2F                  CPL  FE01                CP 01  9F                  SBC A,A  B0                  OR B  A5                  AND L  6F                  LD L,A  7C                  LD A,H  A2                  AND D  67                  LD H,A  CB00                RLC B  ED78                IN A,(C)  38ED                JR O,LOOP  1F                  RRA  CB14                RL H  C9                  RET  

Now - if you enter this subroutine into RAM you can then replace every CDBB02 in the chapter by a call to the appropriate address in RAM. The other subroutine you'll need to be able to emulate is FINDCHR. This may be done as follows.

1600      FINDCHR   LD D,00  CB28                SRA B  9F                  SBC A,A  F626                OR 26  2E05                LD L,05  95                  SUB L  85        LOOP      ADD A,L  37                  SCF  CB19                RR C  38FA                JR C,LOOP  0C                  INC C  C0                  RET NZ  48                  LD C,B  2D                  DEC L  2E01                LD L,01  20F2                JR NZ,LOOP  217D00              LD HL,KTABLE-1    <- NEW ROM ONLY  216B00              LD HL,KTABLE-1    <- OLD ROM ONLY  5F                  LD E,A  19                  ADD HL,DE  C9                  RET  

The address 007D, referred to in my listing as KTABLE-1, is for the NEW ROM only. THE ADDRESS OF KTABLE IN THE OLD ROM IS 006C, and so this line should be changed to LD HL,006B. This is far easier to understand than the first subroutine. The second and third lines are rather interesting.

If you remember BC should contain a code corresponding to one of the keys at the start of the subroutine. Now bit zero of B is a one if SHIFT is not pressed, and zero if shift is pressed. SRA B will shift B to the right, will set bit 7 to one (do you remember the difference between SRA and SRL?), and will set the carry flag equal to the previous value of bit zero.

SBC A,A will first of all subtract A from A - effectively resetting it to zero - and will then subtract the carry flag. In other words, if SHIFT is pressed A will end up as 00, if SHIFT is not pressed A will end up as FF.

The fourth line, OR 26, will ensure that A is 26 for a shifted character, FF for a non-shifted character.

You should recall here that B contains information about which VERTICAL section the key is in, and C about which HORIZONTAL section. If you take a closer look at the order the characters are stored [in] in the keyboard table (KTABLE) which was shown a few pages back you'll see that the horizontal-section-number needs to be multiplied by five, and the vertical-section-number added to it, in order to find a specific key in the table. This is what the next part does:

L is loaded with 5 - the multiplying factor. Notice how the next two lines cancel each other out the first time round the loop. This is one way of adding L nought times should we need to. The next two lines are SCF and RR C. This is not the same thing as SRA C, since bit 7 could be zero (i.e. if a horizontal-section-7 key is pressed). Apart from shifting C to the right it also moves one bit into the carry. If this bit is a one we haven't found the right section yet and the loop is re-executed. Note that five is added each time round the loop. Note also that if A starts off as FF it is just as easy, if not easier, to think of it as minus-one.

Now that we're out of the loop, C should be all ones, that is, it should be FF, so that INC C should ensure that it becomes zero, so what's this RET NZ instruction for? Of course this condition is simply to check that you're not holding down two keys at once. What would C contain if you were?

LD C,B moves the vertical-section-data into the B register, so that the same loop may be used over again.

DEC L followed by LD L,1 looks confusing. Actually it's not. At the moment L is five, and so DEC L makes it four, which is NOT ZERO. LD L,1 doesn't alter the zero flag, so JR NZ,LOOP sends it back through the same loop, but this time checking the vertical sections, and only incrementing by one instead of five.

When it comes out of the loop DEC L will reduce to zero, so after reloading L with one JR NZ will not be satisfied and the program will continue.

LD HL,KTABLE-1. Why minus one? Well if there was a "real key" in the position where SHIFT is and you were pressing it then A would end up as zero. Since there isn't the smallest value A can end up as is one, which happens if you hold down "Z", hence LD HL,KTABLE-1 takes this into account.

LD E,A is effectively loading A into DE. This works because D is already zero - see the first line of the program. Then ADD HL,DE will find the correct address. Notice that we could have replaced these two instructions by ADD A,L followed by LD L,A. This has the advantage that the first instruction (LD D,00) becomes unnecessary, and that DE is not at all altered by the subroutine. The ROM however uses the version as listed.

KTABLE in the OLD ROM looks like this:

006C    ZXCV ASDFG QWERT 12345 09876 POIUY newline LKJH space .MNB  0093    :;?/ graphic-ASDFGQWERT NOT AND THEN TO cursor-left RUBOUT HOME          cursor-right cursor-up cursor-down *)($" edit =+- ** £,>< OR  

For the actual printing process itself, the instruction CALL PRINT for the NEW ROM should be replaced by PUSH AF/CALL PRPOS/POP AF/CALL PRINT. In HEX this is F5/CDE006/F1/CD2007.

The Character Table (CTABLE) which gives the dot patterns for the characters is located in the OLD ROM at address 0E00, rather than 1E00. Again it is stored at the very end of the ROM. All of the characters are slightly different.

The data for the table of graphics symbols in the character printing program should run 00 07 06 03 05 82 08 84 04 88 02 85 83 86 87 80 if the program is to be used with an OLD ROM. Replace the PAUSE 40000 BASIC statement given in the following text [previous listing] by INPUT A$.

[Download available for 16K ZX81 -> chapter10-enlarge2.p. Addresses used: DATA is 4D00, START is 4D10, KSCAN is 4D61 and FINDCHR is 4D82.]

[Download available for 16K ZX80 -> chapter10-enlarge.o. Addresses used: 4A1A to 4B3A is occupied by HEXLD3, DATA is 4B3B, START is 4B4B, KSCAN is 4BA5 and FINDCHR is 4BC6. Unfortunately I can't get this version to work. There's a possibility I may return to this to see if I can fix it, but I've already spent a lot of time checking it over. Feel free to send me your working version and I'll credit you in the page :)]

GRAFFITTI

It only requires a slight modification to the original version in order to make a really excellent program, demonstrating the immense speed which machine code offers over BASIC. In this program, GRAFFITTI, you touch a key and an enlarged version of the required symbol appears on the screen. In this program the whole screen is used (even the bottom two lines) thus allowing a total of forty-eight symbols on the screen. To load it move RAMTOP to any address not less than 4D00 and NEW (i.e. this can't be done in 1K - at least not in this version). The program is as follows:

2A0C40    GRAFFITTI LD HL,(D_FILE)     Set the print position  23                  INC HL             to the start of the screen.  220E40              LD (DF_CC),HL  36B0      START     LD (HL),B0         Print a cursor.  CDBB02    PAUSE     CALL KSCAN         Wait for human to take  2C                  INC L              finger off of key.  20FA                JR NZ,PAUSE  CDBB02    WAIT      CALL KSCAN         Wait for new key to  44                  LD B,H             be pressed.  4D                  LD C,L  51                  LD D,C  14                  INC D  28F7                JR Z,WAIT  CDBD07              CALL FINDCHR       Locate the correct  7E                  LD A,(HL)          character code.  A7                  AND A  17                  RLA                Multiply by eight, but  17                  RLA                return to BASIC if a  D8                  RET C              non-printable character  17                  RLA                is pressed.  1600                LD D,00  CB12                RL D  5F                  LD E,A  21001E              LD HL,CTABLE       Find start of dot  <- NEW ROM ONLY  21000E              LD HL,CTABLE       Find start of dot  <- OLD ROM ONLY  19                  ADD HL,DE          pattern.  0E04                LD C,04  0604      OUTERLOOP LD B,04  56                  LD D,(HL)          Transfer two lines of  23                  INC HL             dots into D and E.  5E                  LD E,(HL)  23                  INC HL  E5                  PUSH HL  AF        INNERLOOP XOR A              Computer which graphics  CB12                RL D               character is to be  17                  RLA                printed.  CB12                RL D  17                  RLA  CB13                RL E  17                  RLA  CB13                RL E  17                  RLA  21-DATA-address     LD HL,DATA         Get this character from  85                  ADD A,L            the table of graphics  6F                  LD L,A             symbols.  7E                  LD A,(HL)  2A0E40              LD HL,(DF_CC)      Print this character  77                  LD (HL),A          in required position.  23                  INC HL             Store new print  220E40              LD (DF_CC),HL      position.  10E3                DJNZ INNERLOOP  D5                  PUSH DE            Move print position  111D00              LD DE,001D         ready for next row  19                  ADD HL,DE          of symbols.  220E40              LD (DF_CC),HL  D1                  POP DE  E1                  POP HL  0D                  DEC C  20CF                JR NZ,OUTERLOOP  1180FF              LD DE,FF80         Move print position  2A0E40              LD HL,(DF_CC)      ready for next enlarged  19                  ADD HL,DE          character.  220E40              LD (DF_CC),HL  7E                  LD A,(HL)          Check for end of line.  FE76                CP 76  209B                JR NZ,START  116400              LD DE,0064         Move print position to  19                  ADD HL,DE          left of screen ready for  220E40              LD (DF_CC),HL      next enlarged character.  23                  INC HL  ED5B1040            LD DE,(VARS)       Check for end of screen.  ED52                SBC HL,DE  19                  ADD HL,DE  383A                JR C,START  C9                  RET  

[Download available for 16K ZX81 -> chapter10-graffitti.p. Addresses used: DATA is 4D00, GRAFFITTI is 4D10, KSCAN is 4D8E and FINDCHR is 4DAF. It's supposed to fill the entire screen but it only wraps one line. When I have some spare time I'll return to this and attempt to fix it.]

This program is intended to be run on a ZX81 in the SLOW mode. See if you can work out how to adapt it so that it will print inverse characters instead of ordinary ones. It may even be possible to offer a choice!

 

 

Draughts Part One

DRAUGHTS

Now that we can enter and edit machine code, it's about time we started using it for something useful, and hopefully interesting. Draughts is a program we have to be very careful with. Here's what it will look like in BASIC:

1 INPUT A$  2 RAND/RANDOMISE USR(something)  

As you can see, the vast, vast majority of it will be entirely in machine code. The machine code will begin immediately after the BASIC program ends. However, in order that we may edit it we shall temporarily store it a little higher up in memory than that - in the fourth K.

Also, in order that we may have the BASIC part of the program at the right location it will be necessary to MOVE the machine code part of HEXLD3. NEW ROM users should start typing POKE 16389,74 and then NEW, and then load the program HEXLD3.

Now, to move it, write the following program to the few spare characters at the end of the REM statement:

010002    MOVE      LD BC,0200  11004A              LD DE,4A00  210040              LD HL,4000  EDB0                LDIR  C9                  RET           Run this.  

Now for the tedious bit. Every address used in the machine code part either begins with 40 or 41. You'll have to go through the listing and change each 40 to a 4A, and each 41 to a 4B (the changes are to be made in the copied version, not the original version). That done change every address in the BASIC parts that calls a USR routine. To make a change you must add 2560 to each number. Now delete line one by typing its line number. The program should still work, but you'll need to type RUN 400 in order to SAVE it. Make sure that the variable BEGIN (now at 4A3C or 4A93) contains a value of 4A00. New ROM users change line 500 to:

500 RAND USR (PEEK 16400+256*PEEK 16401+161) -In this way RETRIEVE  is called from within the variables area, i.e. address (VARS)+A1.  500 POKE 19091,PEEK (PEEK 16400+PEEK 16401*256+6+130+17)  501 POKE 19092,PEEK (PEEK 16400+PEEK 16401*256+6+130+18)  502 POKE 19095,PEEK (PEEK 16400+PEEK 16401*256+6+130+21)  503 POKE 19096,PEEK (PEEK 16400+PEEK 16401*256+6+130+22)  504 RAND USR (PEEK 16400+PEEK 16401*256+6+130+155) -In this way RETRIEVE  is called from within the variables area, i.e. address (VARS)+6+82h+9B.  

[Thunor: The above NEW ROM modifications were discussed earlier in chapter 9's addendum.]

[Download available for 16K ZX81 -> chapter11-hexld3d.p. This is a version of HEXLD3 which includes all of the modifications outlined above i.e. BEGIN (at 4A93) contains the value of 4A00 and HPRINT is at 4A82 etc.]

[Thunor: OLD ROM users, please read this before proceeding: DIM is limited to 255 (try DIM O(256) and see for yourself) which equates to 256 elements * 2 bytes = 512 bytes maximum. The last instruction in this chapter is at 4DF0 and with HEXLD3 starting at 4A1A means that 983 bytes must be catered for. Therefore the version that the author is presenting in this book cannot be saved with HEXLD3.]

Now type RUN 100 to start the WRITE routine and re-enter the board printing routine. Again you'll need to load it to address 4C09. The listing is the same as it was before. Turn to chapter seven and simply retype the whole thing.

The instruction RAND USR 19477 should now print a picture of a draughts board in the top left hand corner of the screen. Try it and see. Now each part of this program will be explained in great detail, so don't worry if a program this size seems a daunting prospect. Right now we are only going to input the first part. It starts off with some data.

4C97: FAFB0605  TABLE      DEFB FA FB 06 05  

[Thunor: The "4C97:" on the left is the memory address to write to and is a convention used throughout the remainder of the book.]

This represents the directions in which we are about to allow moves. The numbers in the data are -6, -5, 6 and 5, which, in the board numbering system the computer will use, are simply the numbers we add to one square to reach another.

The first, and simplest thing to do, is to make a copy of the board as it appears on the screen. The copy is called WKBOARD, for it is the part of RAM on which the computer will do its working out. The address of WKBOARD is to be 403C. That's not a misprint, it really does say four zero three C. For OLD ROM users this is just beyond the end of the BASIC part of the program, but for NEW ROM users it is slap bang in the middle of the system variables. Is this wise?

We will in fact be overwriting the 33 byte area PRBUFF and part of the calculating store MEMBOT. This doesn't matter since we will not be using LPRINT, not be attempting to use floating point calculations, and in fact not using this area at all. This will not cause a crash.

During the construction of this program, OLD ROM users should use the address 4B3C instead, since 403C is in mid-program. You can always change it when your program is complete.

Here's the copying routine. You should load this to address 4CE4.

2A0C40    BOARDCOPY LD HL,(D_FILE)    Make a copy of the board  110D00              LD DE,000D        from the screen to the  19                  ADD HL,DE         working area.  113C40/4B           LD DE,WKBOARD  062A                LD B,2A  EDA0      NSCOPY    LDI  23                  INC HL  10FB                DJNZ,NSCOPY  C9                  RET  

Notice the way LDI was used instead of LDIR. This is a very useful way of saving space. What we are doing is incrementing HL each time round, so that only the black squares are copied, not the white ones. This loop is repeated 2A (forty-two) times, since in addition to the squares on the board, one or two characters from the border are also copied. Notice that although LDI decrements BC, it is C that is decremented, not B, so that the DJNZ instruction will still count correctly.

OLD ROM users can easily check that the routine is working by listing from 4B3C using HEXLD3, after the program is run. NEW ROM users can check by replacing the RET instruction to [with] LD HL,WKBOARD/LD (ADDRESS),HL/JP HLIST. You must not return to BASIC (NEW ROM users that is) since PRBUFF will be wiped out by doing so. You can quite safely return after you've listed.

The next part of the program is just as simple. If you take a look at the board printing program, you'll see that the last thing printed is a row of fourteen spaces. What this is is a "window" in which our machine code program can display messages to the user, so the next thing to do is to fill this window with spaces in order to wipe out any error message that may have been there.

4CF5: 000000    NEXTLINE  six NOPs  4CF8: 000000  4CFB: 2A0C40    CLWIND    LD HL,(D_FILE)    Find start of window.  4CFE: 117000              LD DE,0070  4D01: 19                  ADD HL,DE  4D02: 060E                LD B,0E           Fill it with fourteen  4D04: 3600      WIPEOUT   LD (HL),00        spaces.  4D06: 23                  INC HL  4D07: 10FB                DJNZ WIPEOUT  4D09: C9                  RET               Return to BASIC.  

Notice that we have actually overwritten the previous routine's RET instruction, so that it will automatically continue into this one.

    1   2   3   4   5   6   7   8    +---+---+---+---+---+---+---+---+  1 |   |/W/|   |/W/|   |/W/|   |/W/| 1    +---+---+---+---+---+---+---+---+  2 |/W/|   |/W/|   |/W/|   |/W/|   | 2    A         B    +---+---+---+---+---+---+---+---+       \       /  3 |   |/W/|   |/W/|   |/W/|   |/W/| 3      \     /    +---+---+---+---+---+---+---+---+         \   /  4 |///|   |///|   |///|   |///|   | 4        \ /    +---+---+---+---+---+---+---+---+           X  5 |   |///|   |///|   |///|   |///| 5        / \    +---+---+---+---+---+---+---+---+         /   \  6 |/B/|   |/B/|   |/B/|   |/B/|   | 6      /     \    +---+---+---+---+---+---+---+---+       /       \  7 |   |/B/|   |/B/|   |/B/|   |/B/| 7    D         C    +---+---+---+---+---+---+---+---+  8 |/B/|   |/B/|   |/B/|   |/B/|   | 8    +---+---+---+---+---+---+---+---+      1   2   3   4   5   6   7   8  

The next part is for NEW ROM users only. OLD ROM users please ignore it.

The following will cause line one (that is BASIC line one) to be re-executed as soon as the next RET instruction is received. Note that this overwrites the six NOPs in the previous section.

[NEW ROM ONLY]  4CF5: 217D40    NEXTLINE  LD HL,FIRSTLINE  4CF8: 222940              LD (NXTLIN),HL  

This fools the ROM into thinking that the next line to be executed begins at address 407D, which is the first byte of the program. It doesn't return to BASIC immediately however, it will continue with draughts until a RET instruction is reached.

Now the program seriously starts. We assume that a move has been input as A$, which is the first item in the variable store.

Here's how to input a move. Look at the diagram of the board. There are sixty-four squares, but only thirty-two of them are playable. Each square has a coordinate from 11 to 88. Notice that these are printed without seperation. The first digit refers to the number down the left (and right) hand side of the board, and the second digit refers to the number along the top or bottom of the board.

There are four different directions you may move in. These are called A (up-left), B (up-right), C (down-right) and D (down-left). This is indicated on the diagram. To input a move simply type in the coordinates and a letter (A, B, C or D). There should be no spaces in this input. For instance, to move from square 61 to 52 you should input "61B".

Now for a program to interpret this input. Follow this carefully:

4D09: 2A1040    MOVE      LD HL,(VARS)    <- NEW ROM ONLY  4D09: 2A0840    MOVE      LD HL,(VARS)    <- OLD ROM ONLY        23                  INC HL        7E                  LD A,(HL)        3D                  DEC A        3D                  DEC A        3D                  DEC A        2001                JR NZ,NOTZERO        2F                  CPL  4D14: 5F        NOTZERO   LD E,A  

A small amount of additional explanation concerning the input here, which applies to OLD ROM users only. To input a simple move, such as from 61 to 52, you in fact need to input "shift W space 61B". A simple move must always be preceded by shift W space, and this also applies to single jumps. Double jumps, triple jumps etc. are a little different, and we shall cover them later. As I have said, this is for OLD ROM users only.

The above routine initially loads A with the length of the input string, and then subtracts three, so that for an ordinary move A ends up as zero, for a double jump A ends up as one, for a triple jump A ends up as two, and so on. Then IF A is 00 it is changed to FF. This is so that we can check up on whether or not a player has made a move, or a jump, later on in the game.

This quantity, which is ordinarily FF, is stored in the register E. We then continue.

4D15: 23                  INC HL           The first character of the        23                  INC HL           coordinates is found.        7E                  LD A,(HL)        47                  LD B,A           This number is multiplied        87                  ADD A,A          by eleven, since the board        4F                  LD C,A           on screen is eleven        87                  ADD A,A          characters across.        87                  ADD A,A        23                  INC HL           The position within the        E5                  PUSH HL          string is stored.        80                  ADD A,B        81                  ADD A,C        86                  ADD A,(HL)       The next coordinate is added.        1F                  RRA              Divide by two, since the                                             copy contains only the black                                             squares.        3805                JR C,NOERROR1    If the coordinate points to                                             a black square there is no                                             cheating.  

In the above routine the first coordinate is multiplied by eleven, by making use of registers B and C, and then the second coordinate is added. Note that if you input "12" as your square then because of the Sinclair character codes the program thinks that the first coordinate is actually 1D, and that the second coordinate is 1E. This actually leads to a result of 5D. Rotating right gives 2E, together with a carry indicating that the player has not cheated by giving a white square instead of a black one. The next five bytes deal with what happens if the player has cheated. These are:

4D25: E1        ERROR1    POP HL        Restore the stack pointer.        CDA74C              CALL ERROR    Call to an error message        1D                  DEFM "1"      subroutine.  

The subroutine ERROR, which requires one byte of data (here the byte 1D, the character code of "1") looks like this:

4C9B: 2E31312A  IMOVE     DEFM "ILLE"       These are the words "ILLEGAL        2C263100            DEFM "GAL "       MOVE" - data to be printed.        32343B2A            DEFM "MOVE"  4CA7: E1        ERROR     POP HL            Fetch the byte of data.        7E                  LD A,(HL)        2A0C40              LD HL,(D_FILE)    Find the start of the window.        117000              LD DE,0700        19                  ADD HL,DE        EB                  EX DE,HL        219B4C              LD HL,IMOVE       Copy the words onto the        010C00              LD BC,000C        screen.        EDB0                LDIR        13                  INC DE            Print the byte of data onto        12                  LD (DE),A         the screen.        C9                  RET               Return to BASIC.  

Notice what happens. The message "ILLEGAL MOVE 1" appears on the screen, and no piece is moved. The player is then required to re-input [his/]her move which will then be checked in exactly the same way.

If no error is found (yet) the program continues.

4D2A: C60E      NOERROR1  ADD A,0E    Find the position of the                                        square in WKBOARD.  

0E is simply the required factor to exactly match A to the low part of the address of the working-board square. For instance, adding 0E to 2E gives 3C, and 403C is the start of WKBOARD.

4D2C: 6F                  LD L,A        2640/4B             LD H,WKBOARD-high        4E                  LD C,(HL)          Find which piece is on                                               that square.        0680                LD B,80            Replace that square by a        70                  LD (HL),B          black empty square.  4D33: E3        LOOP      EX (SP),HL         Store the square position,                                               and retrieve the pointer                                               to the input string.        23                  INC HL        227B40              LD (POINTER),HL    Store value. <- NEW ROM ONLY        222240              LD (POINTER),HL    Store value. <- OLD ROM ONLY        7E                  LD A,(HL)        C671                ADD A,71           Find the direction being        6F                  LD L,A             moved from the TABLE.        264C                LD H,TABLE-high        56                  LD D,(HL)        E1                  POP HL             Retrieve square position.        78                  LD A,B             Check whether the player is        A2                  AND D              moving one of her own pieces,        2F                  CPL                and in a legal direction.        A1                  AND C        FE27                CP 27        20DE                JR NZ,ERROR1  

A brief explanation of the last six lines here. A is loaded by [with] 80, D is the direction to be moved, which will be FA, FB, 05, or 06. AND D will therefore produce 00 for a backward direction, and 80 for a forward direction. CPL will change this to FF or 7F. C is the piece to be moved. If it is a black king it will be 27, if it is a black piece it will be A7, so AND D will produce a value of 27 if EITHER the piece being moved is a king, OR if the piece is moving forwards. If you try to move a piece backwards, or give a square which does not contain one of your own pieces, then a value of 27 will NOT be produced. In this case the program will send an "ILLEGAL MOVE 1" error message.

4D48: 7D                  LD A,L           Find destination square.        82                  ADD A,D        6F                  LD L,A        7E                  LD A,(HL)        Check the contents of that sq.        B8                  CP B             Is it an empty square?        2008                JR NZ,NEXT        7B                  LD A,E           If so, is this a single        3C                  INC A            move?        2815                JR Z,CONTINUE        CDA74C              CALL ERROR       If not a single move, give        1E                  DEFM "2"         "ILLEGAL MOVE 2" message.  4D57: B0        NEXT      OR B        FEBC                CP BC            Does square contain a        2804                JR Z,NOERROR3    computer's piece?        CDA74C    ERROR3    CALL ERROR       Give message "ILLEGAL MOVE 3"        1F                  DEFM "3"         if not.  4D60: 70        NOERROR3  LD (HL),B        Overwrite computer's piece                                             with a black empty square.        7D                  LD A,L           Find next destination        82                  ADD A,D          square.        6F                  LD L,A  4D64: 7E        CONTENT   LD A,(HL)        Find the contents of the                                             new square.        B8                  CP B             Is this square empty?        20F4                JR NZ,ERROR3     Give "ILLEGAL MOVE 3" if not.  4D68:           CONTINUE  

At this stage the program will jump or move as the case may be (in other words it will decide for itself - you don't need a special input) and will so far check for three types of error. These are:

  • Attempting to move a piece that is not your own, or moving one of your own non-king pieces backwards.
  • Attempting to make a non-jump move in the middle of a multiple move sequence.
  • Attempting to move to a square which is non empty.

You may like to check all of these things. This isn't too difficult to do. Simply write JP 4DDE to the end of the program and add the following routine:

4DDE: 2A0C40    BDPRINT   LD HL,(D_FILE)        110D00              LD DE,000D        19                  ADD HL,DE        EB                  EX DE,HL        213C40/4B           LD HL,WKBOARD        062A                LD B,2A        EDA0      LDI       LDI        13                  INC DE        10FB                DJNZ LDI        C9                  RET  

This will copy the computer's working-board back onto the screen so that you can see what has happened. You can also alter the data for the board-print routine, and so set up a board in mid-game in order to test some of the error checks if you want.

To make the program run, add the following BASIC program lines:

OLD ROM                   NEW ROM  1 INPUT A$                1 INPUT A$  2 RANDOMISE USR(19683)    2 RAND USR 19683  2 RANDOMISE USR(19684)    2 RAND USR 19684  3 RUN                     3 STOP  4 RANDOMISE USR(19477)    4 RAND USR 19477  5 RUN                     5 RUN  

The program is activated by the command RUN 4. Don't forget you can still use HEXLD3 to list, but you must now use RUN 10 to bring this into operation. If you type RUN on its own accidentally you will get an input prompt. Break out immediately! If you don't the results will be unpredictable. I don't think it will crash, but just to be on the safe side....

And now a check to determine whether or not the human player has reached the other end of the board. If so, this next routine will automatically change [his/]her piece into a king.

4D68: 7D        CONTINUE  LD A,L           If the low part of the        FE40                CP 40            current address is less than        300C                JR NC,NOKING     40 hex then the other side                                             has been reached.        7B                  LD A,E           If this is not the last        3C                  INC A            move then give an "ILLEGAL        FE02                CP 02            MOVE 4" message.        3804                JR C,NOERROR4        CDA74C              CALL ERROR        20                  DEFM "4"        0E27      NOERROR4  LD C,27          Make piece a king.        71        NOKING    LD (HL),C        Put back on board.        E5                  PUSH HL          Store current position on                                             board.  

Notice the new error check. If a player attempts to make a king in mid-move, that is, if [he/]she jumps to the back row and intends to jump out again in the same go, then an error will be detected and "ILLEGAL MOVE 4" [will be] printed to the screen. This is because according to official rules a piece does not become a king until after you remove your fingers from it. Of course in this game your fingers are never on the piece in the first place, but we presume that this is what the rules are intended to mean.

Remember that E contains FF for a single jump, and 01 for a double jump. LD A,E/INC A/CP 02 will only give an error if E is one or more. If E is 00 (which if you've input a multiple jump it will eventually be), the move will go ahead successfully.

4D7B: 2A7B40              LD HL,(POINTER)    Retrieve the <- NEW ROM ONLY  4D7B: 2A2240              LD HL,(POINTER)    Retrieve the <- OLD ROM ONLY                                               position pointed to in the                                               input string.        1D                  DEC E              Decrease the number of moves                                               left in a multi-jump seq.        7B                  LD A,E             Check whether last move        E3                  EX (SP),HL         has been made.        17                  RLA        30AE                JR NC,LOOP         The input pointer is replaced                                               at the top of the stack                                               ready for the next time                                               round the loop.        E1                  POP HL  4D85: C3DE4D              JP BDPRINT         Exit.  

Well, all of the possible error checks have been made, and the program contains a loop which will allow for the inputting of multiple jumps. Here's how a multiple jump should be input. To jump from square 63 first in direction A, then in direction B, then finally in direction C, just input "63ABC" - it's that simple. OLD ROM users need to note the following convention though:

OLD     single moves or single jumps should be preceded by shift W space.  ROM     double jumps should be preceded by shift E space.  ONLY    triple jumps should be preceded by shift R space.          4-ply jumps should be preceded by shift D space.  

And so on... The sequence is W, E, R, D, F, S, A, T, G. I doubt very much whether you'll ever need a 4-ply jump though. Even using a triple jump seems rather unlikely.

The next thing that should happen is that the computer should make a move in response, but we'll leave that to another chapter, since it has a bit of decision making to do. But there is one question to be answered first. What if it now has no pieces left to move? What if the player's last move removed its last piece? This has to be checked for. If this is the case then the player has won, and we must somehow indicate this.

Here is the final check:

4D85: 0EBC                LD C,BC  4D87: CDBC4C              CALL GAMEOVER  4D8A: C3DE4D              JP BDPRINT  

And the subroutine....

4CBC: 213C40/4B GAMEOVER  LD HL,WKBOARD     Look at first square.        062A                LD B,2A        7E        POSSIBLY  LD A,(HL)         Find contents of square.        F680                OR 80             Make it an inverse graphic.        B9                  CP C              Is it what we're looking        C8                  RET Z             for? If so we're OK and                                              can return to draughts.        23                  INC HL            Look at next square.        10F8                DJNZ POSSIBLY     Try again.  4CC9: the next six bytes are for the NEW ROM only. OLD ROM users should        replace them with six NOPs (00).        219740    STOPPROG  LD HL,STOPLINE    Fool the ROM into thinking        222940              LD (NXTLIN),HL    that line 3 is to be carried                                              out next.  

Now we reach the exciting bit. What happens if the player HAS won? I'm not actually going to tell you - just input it and find out. To test it you'll have to alter the data that sets up the initial board, and arrange it so that you can take all of the computer's pieces.

4CCF: 2A0C40    INVERT    LD HL,(D_FILE)        066C                LD B,6C        23        COVER     INC HL        7E                  LD A,(HL)        FE25                CP 25        3006                JR NC,NOINV        A7                  AND A        2803                JR Z,NOINV        F680                OR 80        77                  LD (HL),A        10F2      NOINV     DJNZ COVER        E1                  POP HL        C9                  RET  

Notice how, in the last two lines the return address is removed from the stack, so that the next item on the stack is the return to BASIC address. The next RET will of course do just that.

[Download available for 16K ZX81 -> chapter11-draughts1.p.]

[Thunor: Unfortunately due to the BASIC program and the O$ array expanding in size there isn't enough space remaining below 4A00 to include REM based instructions, therefore I'm printing them here:

NEW ROM  ***********************  2009-09-13  CHAPTER11-DRAUGHTS1  -----------------------  TO RUN MACHINE CODE:  RUN 4  -----------------------  LIST:   RUN 10  WRITE:  RUN 100  INSERT: RUN 200  DELETE: RUN 300  SAVE:   RUN 400  ***********************  

Instructions end.]

 

 

 

A Touch of Culture

MUSIC

Music from your TV speaker? Is it possible? More to the point - is it possible on a ZX? The answer is yes!

As you know, your machine is designed to work without sound. It does make a kind of horrible buzzing noise, but hardly anything you'd want to make music out of. The manual itself tells us to turn the volume right down so as to cut the noise out completely.

The little computer, on the other hand, has a mind of its own. Completely ignoring its own design specifications it thinks to itself "Anything a bigger computer can do, I can do better", and as a result of this rebellion you'll find that REAL MUSICAL NOTES can be produced with just a tiny speck of machine code.

Those of you who have tried the music routines in Interface are undoubtedly thinking to yourselves "Huh! I've heard this so called 'music' - it's rubbish!" Well I assure you this is not the same thing. The reason? Well one big advantage machine code does have over BASIC is precision - and this program is in machine code, not BASIC. The music is musical. You can even tune it if you have a tuning fork handy.

This is called CATHY'S PROGRAM, dedicated to someone who believes computers should be artful, not just attack you with space invaders. The machine code is best stored in a REM statement. The addresses given in the listing assume you have a NEW ROM machine. If you have an OLD ROM machine all you have to change is the addresses (although you will have to supply two of the subroutines yourself [(KSCAN and FINDCHR)] - see chapter ten).

Cathy's Program

9B897369   NOTES    C  D  E  F        This data represents  00937E005E          -  C* D* -  F*    the various notes that  003B312824          -  C  D  E  F     are available from the  0000362C00          -  -  C* D* -     keyboard.  00000F161E          -  -  A* G* F*  000A0C121A          -  C  B  A  G  000000414C          -  -  -  A* G*  00383C4653          -  C  B  A  G  78         PAUSE    LD A,B            Subroutine causing a  3D         HOLD     DEC A             delay of a precise  20FD                JR NZ,HOLD        length.  C9                  RET  CDBB02     START    CALL KSCAN        Wait until    <- CALL HERE  44                  LD B,H            a key is pressed.  4D                  LD C,L  51                  LD D,C  14                  INC D  28F7                JR NZ,START  CDBD07              CALL FINDCHR      Find which key is being  110440              LD DE,NOTES-7E    pressed.  19                  ADD HL,DE  46                  LD B,(HL)         Select note.  AF                  XOR A  B8                  CP B              Check that this note is  28EB                JR Z,START        not a "pause".  DBFF                IN A,(FF)         Play this note.  CDA940              CALL PAUSE  D3FF                OUT (FF),A  CDA940              CALL PAUSE  18E0                JR START          Go round loop again.  

If you store the whole machine code routine in a single REM statement in line one [(of size 77 characters)], then you only need one more line of BASIC to make the program complete. This is line 2 RUN USR 16558, which calls the machine code from the address labelled START. Delete any extra lines you may have, and SAVE the program a couple of times before you RUN it.

You now have two octaves at your disposal - the keyboard below shows where the notes are. A fair number of tunes may be played quite successfully.

Always run the program in the FAST mode - it's not that the speed makes the notes sound differently - it's simply that the program doesn't work AT ALL when in SLOW.

[Download available for 16K ZX81 -> chapter12-cathys.p. I've moved BASIC line 2 to 3, added 2 FAST and 4 STOP.]

[Download available for 16K ZX80 -> chapter12-cathys.o. Addresses used: 4A1A to 4B3A is occupied by HEXLD3D, NOTES is 4B3B, PAUSE is 4B62, START is 4B67, KSCAN is 4BA5 and FINDCHR is 4BC6.]

The notes as listed in the program are roughly right, but exactly how they sound will depend mainly on your television set (incidentally you may have to alter the tuning slightly to get the best sound quality), so in case you need to "re-tune" the notes, here's how you do it:

The data at the start of the program (labelled NOTES) contains one byte for each note. A zero indicates there is no note on that key. The data is in the following order:

+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+  |    | |    | | C* | | D* | |    | | F* | | G* | | A* | |    | |    |  +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+       +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+     |    | | C  | | D  | | E  | | F  | | G  | | A  | | B  | | C  | |    |     +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+          +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+        |    | | C* | | D* | |    | | F* | | G* | | A* | |    | |    |        +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+      +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+    |    | | C  | | D  | | E  | | F  | | G  | | A  | | B  | | C  | |    |    +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+  
data              key              note  9B 89 73 69       Z  X  C  V       C  D  E  F        lower octave  00 93 7E 00 5E    A  S  D  F  G    -  C* D* -  F*    lower octave  00 3B 31 28 24    Q  W  E  R  T    -  C  D  E  F     upper octave  00 00 36 2C 00    1  2  3  4  5    -  -  C* D* -     upper octave  00 00 0F 16 1E    0  9  8  7  6    -  -  A* G* F*    upper octave  00 0A 0C 12 1A    P  O  I  U  Y    -  C  B  A  G     upper octave  00 00 00 41 4C    nl L  K  J  H    -  -  -  A* G*    lower octave  00 38 3C 46 53    sp .  M  N  B    -  C  B  A  G     lower octave  

To alter the frequency of any note just change the byte of data that represents it. To make a note higher you must decrease the number, and to make it lower you must increase the number.

THE PROGRAM'S DISADVANTAGES

The biggest disadvantage is the lack of a RET instruction anywhere in the program, which means that once you enter the program you can never leave. You can cure this by adding a few lines somewhere near the START label. As an exercise, see if you can adjust the program so that it returns to BASIC whenever the key SHIFT-ZERO (rubout) is pressed (HINT: HL equals FCEF when it returns from KSCAN).

The second disadvantage is that if you press SHIFT while playing notes some very random things seem to happen. See if you can make the shift key inactive (except for breaking out as described above) by adding a SET 0,H instruction somewhere in the program.

ZX music is a fascinating subject, and it is possible to store in data a list of notes to be played, and how long each note is to be played - a tune in other words. I'll leave that one to you though, because the only real way to learn is by experimenting. We'll leave the subject of music altogether now and turn to something slightly different: pictures....

PICTURES

This is yet another program which relies on the artistic ability of the human operator. It is strictly for NEW ROM users ONLY, but it is intended to be run in the FAST mode. You will require at least four-K for this.

The program stores in memory three or more different pictures, and cycles through them one at a time, displaying each on the screen for as long as you want. A "picture" can be anything whatsoever - you can compose it out of graphics symbols, letters, spaces, inverse asterisks - whatever.

The first thing you do it to reserve some memory in which to store these pictures. If you have 4K type POKE 16388,182/POKE 16389,70/NEW for three pictures, or POKE 16388,206/POKE 16389,73/NEW for two pictures. If you have 16K you can find enough room for about twenty pictures. To work out how far down you have to move RAMTOP with 16K just start off with 32768 and subtract 793 for each picture.

Now you're ready: Write the following machine code to a REM statement in line one:

2A0C40    STORE     LD HL,(D_FILE)  11B646              LD DE,PICTURE1  011903              LD BC,0319  EDB0                LDIR  C9                  RET  

The address labelled PICTURE1 refers to those people using 4K. For those same people PICTURE2 would be 49CE and PICTURE3 would be 4CE7. If only two pictures will be used you should omit PICTURE1, not PICTURE3. If you have 16K you have more or less limitless freedom. In the interests of simplicity you could use addresses 5000, 5400, 5800, 5C00, and so on.

Now type POKE 16389,77 followed by CLS if you are using 4K, or if you are using 16K but earlier POKEd 16389 with a number less than 77.

Now write a BASIC program (without deleting line one) which prints a picture. The last line of this program should be RAND USR 16514. 4K users may find themselves running out of space. If this is so you'll just have to give up and make do with two pictures instead of three.

A useful fact to know is that if you make the first line of your program (first apart from the REM that is) POKE 16418,0 then you can print to all twenty-four lines of the screen. Even PRINT AT 23,0; works!

Now delete all the PRINT lines. DO NOT TYPE NEW. Change the address in the machine code to that of a different picture, and write a new BASIC program printing a different picture, again ending in RAND USR 16514. Do this until every picture you wish to cycle through has been stored.

Now move RAMTOP back to the address described in paragraph three. Type NEW. Now you are ready....

For the first time in the book we are going to make use of the PAUSE facility. The instruction CALL PAUSE will display the TV picture indefinitely, or until a key is pressed. To PAUSE for a specific number of TV frames it is necessary to LD (FRAMES) with the required number first. Enter this machine language program:

0602      PICTURES  LD B,number of pictures  21B646              LD HL,address of first picture  C5        NEXTPIC   PUSH BC  ED5B0C40            LD DE,(D_FILE)  011903              LD BC,0319  EDB0                LDIR  E5                  PUSH HL  210001              LD HL,length of pause  223440              LD (FRAMES),HL  CD2902              CALL PAUSE  E1                  POP HL  C1                  POP BC  10E8                DJNZ NEXTPIC  C9                  RET  

This is the complete program. See how it works - the first picture is copied into the display file using LDIR, and the PAUSE subroutine is called from the ROM. Then when the PAUSE is over the next picture is copied onto the screen, and so on. The value of HL is not changed between each picture, since they are stored in memory immediately after each other. If they are not (for instance if you are using easy to remember addresses) you'll need to alter the program slightly. HL should point to the start of a new picture each time round the loop.

The BASIC program to go with this is

10 RAND USR pictures  20 RUN  

In this way you can break out of the program at the end of the sequence. Alternatively you could replace the last RET instruction by JR PICTURES, which would eliminate the need for a second BASIC instruction. You can of course always break out during a PAUSE.

LIFE

In the last program in this chapter we turn the tables slightly. We humans have been artistic for long enough - now it's time to let the computers take their turn....

This program is called LIFE - it is supposed to represent the birth/growth/death cycle of a colony of cells living on a square grid. It produces rather fascinating results. Before your very eyes you see a constantly evolving pattern - starting off totally random - which finishes sometimes with the ultimate death of the cell colony, sometimes with a fixed and unmoving cell structure which has reached equilibrium, and sometimes with a continuous cycle of patterns, called dynamic equilibrium. It really is amazing to watch.

LIFE was invented in 1970 by a man called John Conway of Cambridge University, and it's rather surprising that the Tate Gallery hasn't yet got a copy of it. Although it is in fact about the growth of cells which follow hard and fast mathematical rules it in reality becomes a rather effective pattern generating algorithm.

The principle of LIFE is very simple. A grid - usually square - is dotted with approximately one quarter of its available squares filled with cells. These positions are usually chosen entirely at random. This configuration of the grid is called GENERATION ZERO.

Successive generations are then worked out by a fairly simple to understand principle. Each square on the grid has eight neighbouring squares. These squares either contain another cell or they are empty. Every cell with two neighbouring cells; or with three neighbouring cells, will survive to the next generation, but no other cells will survive. A new cell is born in every empty space which has precisely three neighbouring cells, but no other cells are born. With these fairly simple rules it is rather surprising that the game should produce the rather impressive results that it does.

In this version of LIFE our grid is sixteen by sixteen, because of course sixteen is a fairly easy number to work with in hexadecimal. Further, our grid is rather strangely constructed in a curved space continuum, meaning that every square on the left hand edge is connected to the corresponding square on the right hand edge, and vice versa, also every square on the top edge is connected to the corresponding square on the bottom edge and vice versa.

The program is best run in SLOW, although of course it will run in FAST if you add a PAUSE or INPUT statement.

NEW ROM people are advised to store the machine code in a REM statement. OLD ROM people are advised to store the machine code anywhere but a REM statement, since it contains character 76h. The machine code contains exactly one hundred and thirty nine bytes.

The surrounding BASIC program is

 2 RAND USR START   3 RAND USR NEXTGEN  (4 PAUSE 25 or INPUT A$ - optional extra for FAST users)   5 GOTO 3  

where START and NEXTGEN are addresses in the machine code program. In the following listing we assume that the first address is 4082. You can quite easily change it if you wish.

EF010110  TABLE     DEFB EF 01 01 10  Data representing the displacements  10FFFFF0            DEFB 10 FF FF F0  of the neighbouring squares.    call here    0E10      START     LD C,10           C counts the number of rows printed.  0610      NEWROW    LD B,10           B counts the number of columns.  2A3240    NEXT      LD HL,(SEED)      This next section generates a  54                  LD D,H            random number.  5D                  LD E,L  29                  ADD HL,HL  29                  ADD HL,HL  19                  ADD HL,DE  29                  ADD HL,HL  29                  ADD HL,HL  29                  ADD HL,HL  19                  ADD HL,DE  223240              LD (SEED),HL      The new random-number-seed is  7C                  LD A,H            stored.  FEC4                CP C4             Decide which character to print,  3804                JR C,BLACK        based on choice of random number.  3EB4                LD A,B4  1802                JR CHAR  3E80      BLACK     LD A,80  D7        CHAR      RST 10            Print this character.  10E3                DJNZ NEXT         Same for the next character in the  3E76                LD A,76           row.  D7                  RST 10            Print a newline symbol at the end  0D                  DEC C             of the row.  20DB                JR NZ,NEWROW      Same for next row.    C9                  RET               Generation zero printed completely.  0600      NEXTGEN   LD B,00           B counts the no. of cell positions.  110043              LD DE,DUMP        DE stores the start of the working-                                        area used to compute the next gen.  2A0C40              LD HL,(D_FILE)  E5                  PUSH HL           Stack the start of the display-file.  7E        COPY      LD A,(HL)         Copy the current generation (but  23                  INC HL            not newlines) to the working space.  FE76                CP 76  28FA                JR Z,COPY  12                  LD (DE),A  13                  INC DE  10F6                DJNZ COPY    110043              LD DE,DUMP        Stack the start of the dump.  D5                  PUSH DE  0E00      NEXTCELL  LD C,00           C counts the number of neighbours a                                        particular cell has.  D1                  POP DE  E1                  POP HL            Skip over the next character in the  7E                  LD A,(HL)         display file if it is a newline.  FE76                CP 76  2001                JR NZ,VALID  23                  INC HL  E5        VALID     PUSH HL  EB                  EX DE,HL          Store the position within the dump  E5                  PUSH HL           of the cell being examined in HL,                                        and also stack it.  118240              LD DE,TABLE       Point DE to table of displacements.  1A        NEXDIS    LD A,(DE)         Find displacement.  FE0E                CP 0E             If this "displacement" is 0E we  280B                JR Z,COUNTED      have reached the end of the table.  13                  INC DE            Point DE to next item in table.  85                  ADD A,L           Find neighbouring cell-position.  6F                  LD L,A  7E                  LD A,(HL)         Is there a cell there?  FEB4                CP B4  20F3                JR NZ,NEXDIS  0C                  INC C             Increase count if so.  18F0                JR NEXDIS  E1        COUNTED   POP HL            Retrieve cell position.  79                  LD A,C  FE02                CP 02             Are there less than two neighbours?  380F                JR C,NOCELL       If so no cell appears.  FE04                CP 04             Are there four or more?  300B                JR NC,NOCELL      If so no cell appears.  FE03                CP 03             Are there precisely three?  2803                JR Z,CELL         If so, a cell does appear.  7E                  LD A,(HL)  1806                JR PUT  3EB4      CELL      LD A,B4  1802                JR PUT  3E80      NOCELL    LD A,80           A now contains the right character.  E3        PUT       EX (SP),HL        Retrieve print position.  77                  LD (HL),A         Print character.  23                  INC HL            Move print position along line.  E3                  EX (SP),HL        Retrieve cell-position.  23                  INC HL            Look at next cell-position.  E5                  PUSH HL           Stack the position.  7D                  LD A,L            Check the value of L to find out  A7                  AND A             whether or not we have printed the  20BF                JR NZ,NEXTCELL    last cell-position.  E1                  POP HL            Restore the stack to its original  E1                  POP HL            state and return to BASIC.  C9                  RET  

If you used the same addresses as in the listing then START is 16522 and NEXTGEN is 16562. SAVE the program. Do not RUN it yet because if you do it will crash! NEW ROM users MUST first of all type POKE 16389,67 followed by NEW, and OLD ROM users should ensure that they have at least 2K of memory. You will then have to reLOAD the program from tape.

The first thing you should type is RAND/RANDOMISE. You may now type RUN.

An interesting point about this program is that it is capable of producing its own random numbers. The part labelled NEXT does this - you should study how this is achieved, and by all means use the same principle in your own programs.

LIFE will print out a randomly constructed generation zero in just ONE SECOND when in the SLOW mode. The successive generations will then be produced at the staggering rate of three and a half generations per second! If you find this is much too rapid you can slow it down by adding a few more lines of BASIC - I suggest Let X=0/LET X=X+1/PRINT AT 17,0;X with the last two being inside the loop - this has the added advantage of telling you how many generations have been shown.

[Download available for 16K ZX81 -> chapter12-life.p. As I used HEXLD3D to load this high, the instructions are as follows: Set RAMTOP to 4A00 (18944) with POKE 16389,74/NEW. You don't need to type RAND as I added it to the BASIC program. Type RUN to execute Life and press any key to return to BASIC where you can RUN it again. I did first write Life to a line 1 REM statement but the first 3E76 truncated the BASIC listing, so I wrote it to high memory instead following HEXLD3D. Addresses used: 4A82 to 4B77 is occupied by HEXLD3D, TABLE is 4B82, START is 4B8A (19338), NEXTGEN is 4BB2 (19378) and I relocated DUMP to 4D00.]

Finally you should follow the manner in which this program, unlike some other LIFE programs, calculates each new generation entirely on the basis of the previous one. It does not work out the new first row and then calculate the second row by counting the neighbours in the now-changed new first row, the second row is determined by the previous status of the first row (this is what the area of memory labelled DUMP in the machine code listing is for), thus each new generation is correctly set up.

There are many other pattern generating programs, some much simpler, but none with the elegance of LIFE. If you own 16K you might like to try writing a 24 by 24 LIFE, or even a 24 by 32 version - remember, in machine code there is nothing to stop you printing on the very bottom two lines.

The biggest LIFE you could possibly hope to achieve is 48 by 64 using white quarter-squares for cells, but that would be quite a complicated program. If you feel really enthusiastic you might like to have a bash at this monumental task. I will let that decision rest with your sanity.

The next chapter completes the discussion on DRAUGHTS and leaves you with the horrifying prospect of completing the program....

 

 

 

Draughts Part Two

DRAUGHTS

This is the section which decides upon which is the "best" move the computer can make, after the human's move.

You may have to follow this thinking we are about to embark upon very carefully. Here in brief is a systematic breakdown of the way in which the move is chosen.

We can scan the board, one (black) square at a time, and whenever we find a computer's piece we sit and think about it for a bit. To each move we find possible we assign a numerical value, such that the bigger the number, the better we think the move is. It then follows that to select a move we merely have to choose the one with the highest possible value.

Of course this idea won't let the computer plan ahead - it can only think one move at a time. In order to construct this list of moves, and accompanying numerical values we don't actually have to store every single move we find. Having located a possible move, and worked out its score, what happens is this:

If the score is LOWER than those on the list, the move is ignored.

If the score is EQUAL to those on the list, it is added to the end.

If the score is HIGHER than those on the list, then the list is abolished and a new one started.

In this way the list is always as short as it can possibly be. When the final decision time actually arrives the computer now merely has to select one of these moves at random. Next question - where will the list be stored? Answer The Stack. This simplifies things, but it does mean that we must keep a record of where the start of the list is. We shall store this at address 407B (OLD ROM 4022) and call this quantity LBASE. You will notice that in an earlier part of the program we used 407B/4022 to store a quantity called POINTER. Don't worry - this is quite alright. POINTER is not used in the previous section, and its value does not need to be preserved. LBASE was not used in the last section, and again its value does not need to be preserved. Using the same space twice for two different things is a space-saving trick you should get to know.

The decision making of the computer begins at address 4D8A. The first instruction is LD (LBASE),SP. The start of the list is now preserved. We can play around with the stack now as much as we like, as long as we remember to restore its value before we return to BASIC. The second and third instructions are LD BC,0000 and PUSH BC, which will indicate that there is nothing at all in the current list.

The checking loop thus looks like this. Notice that a new variable SQCHK is used. It is listed as residing at 4077, but OLD ROM owners should replace this address by 401C:

4D8A: ED737B40  BOARDSCAN LD (LBASE),SP    Initialise the list.        010000              LD BC,0000        C5                  PUSH BC        213C40              LD HL,WKBOARD    Scan the board, one square        7E        NXTCHK    LD A,(HL)        at a time.        F680                OR 80        FEBC                CP BC            Have we found a computer's                                             piece?        227740              LD (SQCHK),HL        CA434E              JP Z,EVALUATE        2A7740    KPCHKNG   LD HL,(SQCHK)        2C                  INC L            Have we reached the end        7D                  LD A,L           of the board yet?        FE66                CP 66        20EC                JR NZ,NXTCHK     Loop back if not.  

As you can see, this particular bit is quite straightforward. You only need to (temporarily) add a few extra instructions to avoid crashing. These are:

4E43: C3D54D    EVALUATE  JP 4DD5          These additional lines  4DA9: C3D54D    CHOOSE    JP 4DD5          are temporary only. They  4DD5: ED7B7B40            LD SP,(LBASE)    will stop the program  4DD9: 0EA7                LD C,A7          crashing, but will not  4DDB: CDBC4C              CALL GAMEOVER    run it.  

Can you see that loading SP with (LBASE) eliminates the need to POP everything from the stack before returning. LDing SP will fool the machine into thinking that the stack hasn't changed since we went into the loop.

Now we need to think about what form we want the list to take. Let's examine the problem in reverse. What form would we like the list to take, in order to make removing items from the stack easier.

The first item on the stack should be the number of steps involved in the move - that is one for a single move/jump, two for a double jump, three for a triple jump, and so on. The second item should be the numerical value which the items in the list have been assigned - the priority as we shall call it. Following these items of information we should have the list itself, starting with the square to be moved from, followed by a sequence of one or more directions in which to be moved. Immediately after this the second item in the list in the same form, then the third, and so on...

You'll notice that each thing we need on the stack will only need to be one byte in length. The number of steps cannot possibly be more than 255. The priority can be chosen however we like - we can always make it one byte if we wish. The initial square can be stored by only stacking the low part of its address in WKBOARD. The directions to be moved can be stored in the same manner as before - 05, 06, FA, or FB for plus or minus five or six. In order to make this program as space efficient as we can it makes sense to do just that.

To make a random decision let's assume there are B possible choices. We want therefore to choose a random number between 1 and B - or as we shall do between 0 and B-1. We shall do this by the following means:

4DA9: 3A3440    CHOOSE    LD A,(FRAMES)low    Select a random number        90        REPEAT    SUB B               between 0 and B-1. This        30FD                JR NC,REPEAT        number to be stored in        80                  ADD A,B             the A register.        C3D54D              JP 4DD5  

OLD ROM users should replace the address 4034 by 401E. The final JP 4DD5 is merely a means of exiting the program.

Imagine the list is complete and we are about to remove one item from it. The stack now looks like this:

+-------+---------+-------------------+-------------------> - <----------+  |no. of |priority |initial  direction |initial  direction >   < direction|  |steps  |         |square   one       |square   one       >   < one      |  +-------+---------+-------------------+-------------------> - <----------+  |                                                                        |  sp                                                                   lbase  

If we now use the instruction POP BC, B will contain the priority, and C the no. of steps. The priority is now a redundant piece of information, since it was only needed to construct the list in the first place. C however is very important. In the diagram above C would be one, but it doesn't have to be.

The stack now looks like this - but let's generalise a bit more by assuming there are two steps per move, not one:

+----------------------------+----------------------------> - <----------+  |initial direction direction |initial direction direction >   < direction|  |square  one       two       |square  one       two       >   < two      |  +----------------------------+----------------------------> - <----------+  |                                                                        |  sp                                                                   lbase  

If A is an indication of which of these moves we are to choose then it seems logical that we must remove A of them from the stack. Then the required move would be at the top of the stack. Thus if A is zero we do nothing, otherwise we must use some kind of loop. Can you see that POP HL followed by DEC SP will remove one byte from the stack rather than two, and that INC SP can be used to skip over one of the bytes.

The required loop is this:

4DB0: C1                  POP BC           Find the number of steps        41                  LD B,C           per move.        2808                JR Z,FIRSTOFF    Do nothing if A is zero.        33        NSQOFF    INC SP           Remove a total of A        33        NEXTOFF   INC SP           complete moves from the        10FD                DJNZ NEXTOFF     stack.        41                  LD B,C        3D                  DEC A        20F8                JR NZ,NSQOFF  

The selected move is now at the top of the stack. To carry it out let's first take a look at what the stack is now like:

+------------------------------> - -  |initial  direction  direction >  |square   one        two       >  +------------------------------> - -  |  sp  

To find the initial square the sequence is POP HL followed by LD H,WKBOARD-high. You see "initial square" is the low part of the address. By assigning H with the high part we ensure that the register pair HL contains the absolute address of the square from which we must move. H must be assigned after the POP HL instruction though, since there is no real way we can manage to remove L on its own. Finally the instruction LD B,C once more will assign B with the number of steps we have to make. The procedure for carrying out these steps is much simpler than before since we don't have to check for cheating - we shall write the program such that the computer cannot cheat.

4DBA: E1        FIRSTOFF  POP HL               Find the absolute address  4DBC: E1        FIRSTOFF  POP HL               Find the absolute address        2640                LD H,WKBOARD-high    from which we must move.  

To remove one direction at a time from the stack we shall use the sequence DEC SP/POP DE. In this way E will be assigned with the required direction. D will contain useless information.

4DBF: 41                  LD B,C        3B        NEXTSTEP  DEC SP           Find which direction the        D1                  POP DE           computer is to move.        4E                  LD C,(HL)        Get computer's piece.        3680                LD (HL),80       Overwrite with black sq.        7D                  LD A,L           Find destination square.        83                  ADD A,E        6F                  LD L,A        7E                  LD A,(HL)        Is this square empty?        FE80                CP 80        2805                JR Z,SQUARE      If so, move.        3680                LD (HL),80       If not, jump.        7D                  LD A,L        83                  ADD A,E        6F                  LD L,A        71        SQUARE    LD (HL),C        Put piece in position.        10EB                DJNZ NEXTSTEP    Same for next direction.  

You should now be at address 4DD5, at which is stored the sequence

4DD5: ED7B7B40            LD SP,(LBASE)        0EA7                LD C,A7        CDBC4C              CALL GAMEOVER  4DDE: 2A0C40    BDPRINT   LD HL,(D_FILE)                  ...                  and so on down to  4DF0: C9                  RET  

[Download available for 16K ZX81 -> chapter13-draughts2.p.]

This means that provided the stack is correctly set up we can actually see this whole mechanism working. What I want you to do now is to write a short routine to set up the stack so that all of the possible opening moves are stored. You should be able to do this all by yourself. I will tell you though that the routine should be placed at address 4E43 (what will eventually be the EVALUATE routine) and should be terminated by the instruction JP CHOOSE (C3A94D). One way of doing this bit would be LD HL,something/PUSH HL/LD HL,something/PUSH HL/and so on, but if you can think of a better way by all means use it.

You may now RUN the draughts program by typing RUN 4. You will be asked for an input - make your move as you have been doing in the past. Now watch what happens to the computer's side - one of the pieces should move! Break out of the program, since as yet it can only decide upon the first move of the game.

Now RUN it again - again by typing RUN 4. Does the computer make the same move? If it does it's purely coincidence, since choosing from the list is done at random. Try again, and again, remembering to break out of the prorgam each time and re-run. You should get a different result each time.

We'll leave the program at this stage and continue later on with the mechanism of setting up the stack correctly in the first place, and actually deciding which moves are better than others.

In the next chapter we'll look at some complete (and short) games designed to demonstrate what machine code can achieve in terms of speed, and in very few bytes compared with BASIC.

 

 

 

Graphics Games

SPIRALS

In this fast moving real-time graphics game (intended for use with SLOW) you are placed at the start of a square spiral and must reach the end of it in the minimum possible time. Your score is constantly displayed - it starts of at 99900 and decrements continuously, but you can't cheat by breaking out early with a high score - the program won't allow that. Now and again the score will reach zero before you reach the end of the spiral. If that happens you obviously need more practice!

This fascinating and highly amusing game is unfortunately for NEW ROM users with SLOW only. It will not work in FAST because although the program will still consider itself to be running perfectly smoothly, the average human operator won't know what's going on because of the fact that the screen in front of them is completely black.

This is a fascinating game to watch - witnessing the score decrease before your very eyes is surprisingly effective. You can make the game as difficult as you like by altering the initial value of the "timing" - held in BC. I've given it 0400, but you could use 0800 for a slower game, 0200 for a faster game, and so on.

There is one difficulty built in though - if you hit a wall you don't just bounce off, you actually become embedded in it, and the only way you can get out is to exactly reverse your direction. It can be quite tricky.

Well good luck on your race - keep a record of the high scores (no cheating) and see if you can master it.

The keys will move you as follows: Any key on the bottom row will move you downwards (except for shift, which has no effect), any key on the top row moves you up. The middle two rows move you left and right, with the lefthand ten keys (QWERTASDFG) moving you to the left, and the ten righthand keys (YUIOPHJKLn/l) moving you to the right. This system was adopted instead of using the cursor controls 5, 6, 7, and 8 for two reasons.

  • It is easier for people to understand and become familiar with.
  • It is easier to program, since we only need to test one register after the keyboard scan instead of two.

The program lists as follows, and can be relocated to any desired location by changing just one [two] addresses. The program should be called from the point labelled START.

E1        SPRINT    POP HL              This subroutine prints  7E                  LD A,(HL)           out a picture of the board,  23                  INC HL              along with your initial  E5                  PUSH HL             score. It must however be  FEFF                CP FF               provided with a list of  C8                  RET Z               data terminated by FF.  D7                  RST 10  18F6                JR SPRINT  CD-sprint  START    CALL SPRINT         Calls the subroutine. The                                          following is data for the                                          subroutine.  DEFB 80 80 80 80 80 80 80 80 80 80 80 76       80 15 80 00 00 00 00 00 00 00 00 80 76       80 00 80 00 80 80 80 80 80 00 80 76       80 00 80 00 80 00 00 00 80 00 80 76       80 00 80 00 80 00 80 00 80 00 80 76       80 00 80 00 80 80 80 00 80 00 80 76       80 00 80 00 00 00 00 00 80 00 80 76       80 00 80 80 80 80 80 80 80 00 80 76       80 00 00 00 00 00 00 00 00 00 00 80 76       80 80 80 80 80 80 80 80 80 80 80 76       76       3E 34 3A 37 00 38 28 34 37 2A 00 33 34 3C 00 25 25 25 1C 1C FF    2A0C40    SETUP     LD HL,(D_FILE)      This section initialises the  110E00              LD DE,000E          two "variables" used in our  19                  ADD HL,DE           program.  227B40              LD (POSITION),HL  210000              LD HL,0000  227940              LD (LASTMOVE),HL  2A0C40    LOOP      LD HL,(D_FILE)      Decrement the score.  118B00              LD DE,008B  19                  ADD HL,DE  7E        DECIMAL   LD A,(HL)  A7                  AND A  2008                JR NZ,POSITIVE      ###########  0605                LD B,05             #+#       #  23        RESET     INC HL              # # ##### #  361C                LD (HL),1C          # # #   # #  10FB                DJNZ RESET          # # # # # #  C9                  RET                 # # ### # #  3D        POSITIVE  DEC A               # #     # #  FE1B                CP 1B               # ####### #  2005                JR NZ,OK            #         #  3625                LD (HL),25          ###########  2B                  DEC HL  18EA                JR DECIMAL  77        OK        LD (HL),A  010004              LD BC,SPEED         A timed delay. Altering the  0B        DELAY     DEC BC              initial value of BC changes  78                  LD A,B              the speed of the game.  B1                  OR C  20FB                JR NZ,DELAY  CDBB02              CALL KSCAN          Scan keyboard. L now contains  7D                  LD A,L              a value corresponding to the  2F                  CPL                 direction required.  6F                  LD L,A  E681                AND 81  2805                JR Z,NOTDOWN        Find direction.  110C00              LD DE,000C  181C                JR CHKMOVE  7D        NOTDOWN   LD A,L  E618                AND 18  2805                JR Z,NOTUP  11F4FF              LD DE,FFF4  1812                JR CHKMOVE  7D        NOTUP     LD A,L  E660                AND 60  2805                JR Z,NOTRIGHT  110100              LD DE,0001  1808                JR CHKMOVE  7D        NOTRIGHT  LD A,L  E606                AND 6  28B2                JR Z,LOOP  11FFFF              LD DE,FFFF  2A7940    CHKMOVE   LD HL,(LASTMOVE)    Is player embedded in wall?  7D                  LD A,L  B4                  OR H  2807                JR Z,MOVE  19                  ADD HL,DE           If so, is player reversing?  7D                  LD A,L  B4                  OR H  2802                JR Z,MOVE  18A1                JR LOOP  2A7B40    MOVE      LD HL,(POSITION)    Reassign square with black  7E                  LD A,(HL)           or white space as required.  E680                AND 80  77                  LD (HL),A  19                  ADD HL,DE           Find new position.  7E                  LD A,(HL)           Draw black or white cross  F615                OR 15               as appropriate.  77                  LD (HL),A  227B40              LD (POSITION),HL  210000              LD HL,0000          Store direction moved if  17                  RLA                 a wall has been hit.  3002                JR NC,NOTHIT  62                  LD H,D  6B                  LD L,E  227940    NOTHIT    LD (LASTMOVE),HL  2A0C40              LD HL,(D_FILE)      Check to see whether the  113600              LD DE,0036          finished square has been  19                  ADD HL,DE           reached.  ED5B7B40            LD DE,(POSITION)  ED52                SBC HL,DE  C8                  RET Z  C3-loop             JP LOOP  

[Download available for 16K ZX81 -> chapter14-spirals.p. As I used HEXLD3D to load this high, the instructions are as follows: Set RAMTOP to 4A00 (18944) with POKE 16389,74/NEW. Type RUN to play the game. Addresses used: 4A82 to 4B77 is occupied by HEXLD3D, SPRINT is 4B82, START is 4B8C (19340), LOOP is 4C2D and the timing value SPEED (default 0400) is held at 4C4C which you'll probably want to double to 0800.]

BREAKOUT

In this version of BREAKOUT, which incidentally may only be run on a NEW ROM in SLOW, you move the bat with any of the keys on the keyboard - those on the left will move you to the left, and those on the right will move you to the right. The game is intended to be played only by those people with 3.25K or more, but it can be persuaded to run in less if the following few lines of machine code are added to the program - these should precede the main program:

FD362200  EXTRA     LD (IY+22),00  210003              LD HL,0300  AF        SPACES    XOR A  D7                  RST 10  2B                  DEC HL  7C                  LD A,H  B5                  OR L  20F9                JR NZ,SPACES  

The reason for this is that the main BREAKOUT program assumes that the screen is initially completely full - that is, that it contains twenty-four rows, each consisting of thirty-two spaces followed by a newline. If your machine has less than 3.25K on board then this will not be so, because of the way that the ROM sets up the screen. To rectify this we first LD (IY+22) with 00. IY is always 4000 at the start of any USR routine, so IY+22 is 4022, which is the system variable DF_SZ. This represents the number of rows in the bottom half of the screen (the part we cannot print to) - by telling the machine that this number is zero it follows that the number of rows that we cannot print to is also zero, thus the whole screen is at our disposal. HL counts the number of spaces to be printed to ensure that we do not try to run off the end of the screen.

BREAKOUT is a program in four parts. These parts are:

  • Initialise everything.
  • Restart the game for each new ball.
  • Move the ball.
  • Move the bat.

We will go over each of these steps in scrutinous detail.

Firstly to initialise everything. This involves a) printing the playing board, b) defining the initial ball position, and c) setting the initial speed of the game. To print the board:

20002200 TABLESTART DEFW 0020 0022  E0FFDEFF            DEFW FFE0 FFDE  2A0C40    BREAKOUT  LD HL,(D_FILE)    Load all of the bricks into  118500              LD DE,0085        position.  19                  ADD HL,DE  018080              LD BC,8080        B is the number of bricks, C is a  23        NXBRK     INC HL            constant used quite frequently in  7E                  LD A,(HL)         this section.  FE76                CP 76  28FA                JR Z,NXBRK  3608                LD (HL),08  10F6                DJNZ NXBRK  2A0C40              LD HL,(D_FILE)    Put top wall in position.  061E                LD B,1E           This part puts in the first thirty  23        NXBL      INC HL            blocks.  71                  LD (HL),C  10FC                DJNZ NXBL  23                  INC HL  369C                LD (HL),9C        The current score -zero - is  23                  INC HL            entered.  71                  LD (HL),C         The last block is set in place.  23                  INC HL  23                  INC HL  111F00              LD DE,001F        DE is one more than the number of  0617                LD B,17           spaces between the walls.  71        SIDES     LD (HL),C         Both side walls are loaded into  19                  ADD HL,DE         position.  71                  LD (HL),C  23                  INC HL  23                  INC HL  10F9                DJNZ SIDES  0620                LD B,20           Now the base-line is drawn in.  361B      BASE      LD (HL),1B  23                  INC HL  10FB                DJNZ BASE  

You'll notice that in this version of the game I've ensured that a row of full stops is printed below the very bottom of the screen. This provides a convenient test for whether or not the ball has hit the base. Finally, to set the ball position and speed, the procedure is:

11FCFE              LD DE,FEFC          This is the displacement from                                          the current print position to                                          the ball's starting point.  19                  ADD HL,DE           Locate this starting point.  223C40              LD (BALLINIT),HL    Store it.  210009              LD HL,0900          This is the initial speed.  224640              LD (SPEED),HL       Store it.  

This is actually all the initialisation we need. You'll notice several things missing - for example although the ball is located it is not actually printed. The bat is not mentioned at all! The reason is that the bat is redrawn every time the game is restarted, and so is the ball. Why bother to find the initial position then? Well in this version, the ball starts off in a slightly different position each time. This ensures that it is possible to wipe out all of the bricks.

The variable SPEED has a dual purpose. Firstly it determines the speed of the game - that is, the speed at which the bat and ball will move (the bat moves at precisely twice the ball speed), but secondly it determines when the game is over. When SPEED decrements to zero (the lower the number, the faster the game) we know that the game is over.

Section two of the game does the following tasks:

  • Change the initial ball position, whilst also noting the current ball position and printing the ball.
  • Set the initial direction of movement of the ball to up/right.
  • Change the speed of the game and check for end of game.
  • Print the bat, and at the same time delete any previous bat symbol that may have been there.
  • Give the human player a chance to recover from the last session, since presumably [he/]she won't want one ball to leap into the game immediately [after] the last one vanishes.

The section is this. Look at the manner in which the bat is printed and the previous bat overwritten.

2A3C40    RESTART   LD HL,(BALLINIT)     Change the starting  23                  INC HL               position of the ball.  223C40              LD (BALLINIT),HL  224040              LD (BALLPOS),HL      Start the ball here.  3634                LD (HL),34           Print the ball.  21E0FF              LD HL,FFE0           Set the initial direction.  224440              LD (DIRECTION),HL  3A4740              LD A,(SPEED)high     Increase the speed.  3D                  DEC A  C8                  RET Z                Return to BASIC if lives have  324740              LD (SPEED)high,A     run out.  2A0C40              LD HL,(D_FILE)       Reprint the bat in its  11B702              LD DE,02B7           starting position.  19                  ADD HL,DE  3600                LD (HL),00  3E03                LD A,03              A contains the bat symbol.  23                  INC HL  77                  LD (HL),A  23                  INC HL  77                  LD (HL),A  23                  INC HL  77                  LD (HL),A  224240              LD (BATPOS),HL       Store the initial bat  23                  INC HL               position (this is the  77                  LD (HL),A            position of the centre of the  23                  INC HL               bat).  77                  LD (HL),A  0618                LD B,18              Now erase the rest of the  23        ERASE     INC HL               row, in case a previous bat  3600                LD (HL),00           symbol remains there.  10FB                DJNZ ERASE  210000              LD HL,0000           Set a very long delay, for  1803                JR DELAY             the player to recover for the                                           next ball.  

The last two lines, which cause a short pause between sessions, will become clear when the start of the next section is given.

To move the ball we first of all go through a timed delay loop (controlled by SPEED - the speed of the game) and then unprint the previous position of the ball. The contents of the next square in the direction the ball is travelling are examined, and one of the following will happen:

  • If a full stop has been reached then the ball has gone off the bottom of the screen - the game is restarted.
  • If either a space (i.e. nothing hit) or a brick is located, the ball is reprinted, at this new position.
  • If anything other than a space is reached, the direction of movement of the ball is changed at random.
  • If the ball was notreprinted then find the contents of the next square in this new direction and re-examine the situation.
  • If a brick was hit, the score is increased by 1.

Now, in order that we may choose a new random direction validly we require a table of directions to choose from. These valid directions are 0020, 0022, FFE0, and FFDE. You should store these numbers, low part first, at any address in RAM, and call the start of this table TABLESTART. The program which will then achieve all of this is as follows:

2A4640    LOOP      LD HL,(SPEED)        This is a short delay loop  2B        DELAY     DEC HL               which controls the speed of  7C                  LD A,H               the game.  B5                  OR L  20FB                JR NZ,DELAY  04                  INC B                The ball is only moved every  CB40                BIT 0,B              other time round the loop, so  205A                JR NZ,MOVEBAT        that the bat moves twice as                                           fast as the ball.  2A4040    MOVEBALL  LD HL,(BALLPOS)      Current ball position is found.  3600                LD (HL),00           Erase the ball.  ED5B4440            LD DE,(DIRECTION)    Find next position of the ball.  19                  ADD HL,DE  7E                  LD A,(HL)            Find the contents of this new  FE1B                CP 1B                position.  28A6                JR Z,RESTART         Has the ball hit the base?  4F                  LD C,A               Start next ball if so.  E6F7                AND F7  2005                JR NZ,DONTMOVE       Only reprint the ball if the  3634                LD (HL),34           new position is either empty  224040              LD (BALLPOS),HL      or contains a brick.  B1        DONTMOVE  OR C                 Retrieve previous contents.  283E                JR Z,MOVEBAT         Change direction if not a  E5                  PUSH HL              space.  2A3240              LD HL,(SEED)         Generate new direction at  54                  LD D,H               random.  5D                  LD E,L  29                  ADD HL,HL  29                  ADD HL,HL  19                  ADD HL,DE  29                  ADD HL,HL  29                  ADD HL,HL  29                  ADD HL,HL  19                  ADD HL,DE  223240              LD (SEED),HL  7C                  LD A,H               Choose this direction from a  E606                AND 06               table.  C6tablestartlow     ADD A,TABLESTARTlow  6F                  LD L,A  26tablestarthigh    LD H,TABLESTARThigh  5E                  LD E,(HL)  23                  INC HL  56                  LD D,(HL)  ED534440            LD (DIRECTION),DE  E1                  POP HL  79                  LD A,C               If the contents of the square  FE08                CP 08                is not a brick, then move  20BF                JR NZ,MOVEBALL       again.  2A0C40              LD HL,(D_FILE)       Having established that a  111F00              LD DE,001F           brick has been hit, the score  19                  ADD HL,DE            is increased by one.  7E                  LD A,(HL)  FE80                CP 80  2002                JR NZ,DIGIT  3E9C                LA A,9C  3C        DIGIT     INC A  FEA6                CP A6  2005                JR NZ,INCREASED  369C                LD (HL),9C  2B                  DEC HL  18EF                JR CARRY  77        INCREASED LD (HL),A  

An interesting point to watch for is the way in which the score is increased. Compare the mechanism to that used in SPIRALS to decrease the score. There are one or two differences between this and the last. Firstly of course we are here using INVERSE digits instead of ordinary digits, though this difference is rather trivial. Secondly the BREAKOUT score increases instead of decreases. Thirdly, the SPIRALS score would terminate at zero, whereas the BREAKOUT score can increase indefinitely.

To move the bat, first of all the keyboard is scanned, and if a left-hand key is pressed [then] the bat is moved to the left, provided of course there is not a wall in the way, and if a right-hand key is pressed then the bat is moved to the right, if possible. Note that if a left and right key are pressed simultaneously [then] the bat should not move at all. In our program such a circumstance would cause the bat to move first to the left, and then to the right.

Study this, the final part of the program, and watch the way the bat is actually moved. Remember that the variable BATPOS stores the position of the middle of the bat.

C5        MOVEBAT   PUSH BC           Preserve the value of B.  CDBB02              CALL KSCAN        Scan the keyboard.  C1                  POP BC  7D                  LD A,L  2F                  CPL  F5                  PUSH AF           Stack contains a value  E60F                AND 0F            corresponding to the key pressed.  2817                JR Z,NOTLEFT      If the player moves left....  2A4240              LD HL,(BATPOS)    Locate the bat.  2B                  DEC HL  2B                  DEC HL  2B                  DEC HL  7E                  LD A,(HL)         Is there a wall to our left?  FE80                CP 80             If so, don't move.  2829                JR Z,CYCLE1  3603                LD (HL),03        Extend the bat to the left.  23                  INC HL  23                  INC HL  224240              LD )BATPOS),HL    Store new bat position.  23                  INC HL  23                  INC HL  23                  INC HL  3600                LD (HL),00        Decrease bat to the right.  F1        NOTLEFT   POP AF  E6F0                AND F0  2819                JR Z,CYCLE2       If the player moves right....  2A4240              LD HL,(BATPOS)  23                  INC HL  23                  INC HL  23                  INC HL  7E                  LD A,(HL)         Is there a wall to the right?  FE80                CP 80             If so, don't move.  280E                JR Z,CYCLE2  3603                LD (HL),03        Extend the bat to the right.  2B                  DEC HL  2B                  DEC HL  224240              LD (BATPOS),HL    Store new bat position.  2B                  DEC HL  2B                  DEC HL  2B                  DEC HL  3600                LD (HL),00        Decrease bat to the left.  E5                  PUSH HL           Same for next time round.  E1        CYCLE1    POP HL  C3loop    CYCLE2    JP LOOP  

[Download available for 16K ZX81 -> chapter14-breakout.p. I used HEXLD3D to load this high, but if you set RAMTOP to 4A00 (18944) the game won't run (perhaps HEXLD3D and the game's machine code should have been higher - who knows) so don't set RAMTOP to anything. Type RUN to play the game. Addresses used: 4A82 to 4B77 is occupied by HEXLD3D, TABLESTART is 4B78, BREAKOUT is 4B80 (19328) and LOOP is 4C02. The initial timing value (default 0900) is held at 4BBF which you may want to increase.]

 

 

Draughts Part Three

DRAUGHTS

The story so far... Once upon a time a human being input a move to a ZX computer. The computer checked this move to make sure that no cheating was going on, and cast a wicked spell on the poor human if it was, which meant that the whole move had to be typed in all over again. The move was made. The computer started to search through the board for pieces that it could move. Having found a piece, but not knowing whether or not it could move, it then miraculously found itself at an address called EVALUATE. Where do we go from here?

Let's start off by saying that a neutral move - that is a move which achieves nothing, but also loses nothing - has a "priority" of 80 (hex).

The first point worth noting is that if a piece is in imminent danger of being captured then it stands to reason that we ought to move it out of the way - unless something more important crops up. Secondly, if a piece if preventing another piece from being captured, then we should be less likely to move it. Both of these conditions apply regardless of which direction we consider moving the piece. It stands to reason then that we should work out this part of the priority first, before we start analysing each of the different directions. We must therefore work out a numerical value that corresponds to the square that we are looking at. This value will then be added to 80, after which each direction in turn will be analysed.

EVALUATE will therefore start off

4E43: CDF14D    EVALUATE  CALL SQUAREVAL        C680                ADD A,80        322140              LD (INITIAL),A  

The last instruction stores the value we've found for use later on in the game. On the OLD ROM the address of INITIAL should be changed to 4019. Now let's take a closer look at the subroutine SQUAREVAL. It will assign a value as follows - starting with zero, if a piece is in danger it will add five, or seven for a king. If it is protecting a piece it will subtract five, or seven for a king. Further, the subroutine, as with all subroutines from now on, must not be allowed to alter the values of any register except A. One way of doing this is to begin the subroutine

4DF1: C5        SQUAREVAL PUSH BC        D5                  PUSH DE        E5                  PUSH HL  

Here is the complete subroutine. Follow it through carefully. It should be sufficiently annotated for you to make sense of exactly what's going on.

[Write to 4DF1h.]    C5        SQUAREVAL PUSH BC        Store the current value of the regi-  D5                  PUSH DE        sters on the stack, to be retrieved  E5                  PUSH HL        at the end of the subroutine.  0600                LD B,00        B is being used as a flag here. The                                     first time round the loop it will be        zero, the second time round it will be one. Watch the checks on B        carefully. The loop will check for protection the first time        round, but for danger the second time round.    11974C    STARTOFF  LD DE,TABLE    DE is a pointer, which points to the                                     table of directions of movement.  1A        NOWT      LD A,(DE)  4F                  LD C,A         C now contains such a direction.  D62E                SUB 2E  282A                JR Z,EXIT      If this "direction" is 2E we have                                     passed the end of the table. We                                     should exit with value zero.  1C                  INC E          Move pointer to next direction in                                     table.  E1                  POP HL  E5                  PUSH HL        L contains the low part of the  2640                LD H,40        current square. We retrieve it                                     without altering the stack, and                                     reassign H to the high part of                                     this address.  7D                  LD A,L           81                  ADD A,C        Find square to be looked at in this  CB40                BIT 0,B        direction. Watch how B affects what  2001                JR NZ,LA       happens.  81                  ADD A,C  6F        LA        LD L,A  3E7F                LD A,7F        Watch how A is constructed here. If  B1                  OR C           a human's piece is present A will  A6                  AND (HL)       end up as 27 UNLESS that piece is a  FE27                CP 27          non-king which can't move towards  20E5                JR NZ,NOWT     us. Then it will produce A7. No                                     other piece can generate the result                                     27.  7D                  LD A,L  91                  SUB C          Look at next square towards us. If B  6F                  LD L,A         is zero we are looking at a possible                                     piece being protected. If B is one                                     we are looking at ourselves.  7E                  LD A,(HL)  37                  SCF            This is another way of checking for  17                  RLA            a computer's piece regardless of  CB40                BIT 0,B        whether or not it is a king, but  2006                JR NZ,LB       watch the carry flag.  FE79                CP 79  20D7                JR NZ,NOWT  7E                  LD A,(HL)  17                  RLA  3F        LB        CCF            Now notice the clever way we decide  3E81                LD A,81        on 5 for a piece, or 7 for a king.  17                  RLA  17                  RLA            A now contains 5 or 7 as needed.  CB40      EXIT      BIT 0,B        The loop is now ended.  2006                JR NZ,LC  04                  INC B          This is what happens if B was zero.  67                  LD H,A         The value 5, 7 or 0 is stored on the  E3                  EX (SP),HL     stack behind HL.  E5                  PUSH HL  18C3                JR STARTOFF  57        LC        LD D,A         This is what happens if B was one.                                     D now contains the current value                                     0, 5, or 7.  7D                  LD A,L         The square behind us is located.  91                  SUB C  6F                  LD L,A  7E                  LD A,(HL)      The contents of this square are                                     examined.  FE80                CP 80          If it is not a blank square we are  28BD                JR NZ,NOWT     not in danger.  28BD                JR Z,NOWT      not in danger.  7A                  LA A,D         The current value is retrieved.  E1                  POP HL  D1                  POP DE         D now contains the previous value                                     0, 5, or 7.  92                  SUB D          The final square-value is calculated.  D1                  POP DE         The remaining registers are removed  C1                  POP BC         from the stack.  C9                  RET            End of subroutine.  

This works because if you take a look at the diagram below you'll see very clearly the conditions under which we define a piece as being "in danger" or protecting. Compare carefully what the subroutine does both times round, with each of the diagrams.

                          +------------+                            |            |                            |  human's   |                            |   piece    |       PROTECTING           |            |                            |            |               +------------+------------+            +------------+               |            |                         |            |               | computer's |                         |  human's   |               |   piece    |                         |   piece    |               |            |                         |            |               |            |                         |            |  +------------+------------+            +------------+------------+  |   square   |                         |   square   |  |   being    |                         |   being    |  |   valued   |                         |   valued   |  |     --     |                         |     --     |  |     us     |                         |     us     |  +------------+            +------------+------------+                            |            |                            |            |                            |   blank    |        IN DANGER                            |   square   |                            |            |                            +------------+  

Now for the rest of that decision making routine EVALUATE. It contains a deliberate mistake - see if you can find it (the program will still run perfectly smoothly even with the mistake still in)! If you can't sus it out on your own I'll tell you later on.

This routine is designed to compute a numerical value - a "priority" - for any individual move. Having done so it will compare this priority with those moves stored on the stack. If the new priority is less, it will forget this move and go on to explore a new one. If the new move is equal in priority it will be stored on the stack. If the new priority is more than those on the stack then the list will be abolished, and a new list started.

The registers in the routine have the following jobs:

  • A - a general purpose working register.
  • B - counts the number of items in the list. You may remember the CHOOSE routine earlier on relied on B containing this number of items.
  • C - a general purpose working register.
  • DE - a pointer which looks at the table of allowable directions of movement.
  • H - the direction being moved.
  • L - the low part of the address of the current square.

The routine begins at address 4E43:

[Write to 4E43h.]    CDF14D    EVALUATE  CALL SQUAREVAL     Check for danger and/or  C680                ADD 80             protection at current square.  322140              LD (INITIAL),A    11974C              LD DE,TABLE        Set pointer to start of table.  4D                  LD C,L             Remember low part of the address                                         of current square for later use.  69        NXTMRND   LD L,C             Retrieve the value.  2640                LD H,40            Assign high part of this address.  1A        NXTDIR    LD A,(DE)          Select direction of movement.  1C                  INC E              Move table pointer.  CB7E                BIT 7,(HL)         Check whether or not we are                                         looking at a king.  2804                JR Z,ANYDIR        If so we can move in any direct.  CB7F                BIT 7,A            Check whether current direction                                         is forward or backward.  20F6                JR NZ,NXTDIR       If backward pick a new direction.  FE2E      ANYDIR    CP 2E              If this direction is 2E then we  CAA04D              JP Z,KPCHKNG       have covered all four directions.  C5                  PUSH BC            Temporarily stack B - the number                                         of items in the list of moves.  47                  LD B,A             Store current direction temp.  B1                  ADD A,C            Find the address of the dest-  6F                  LD L,A             ination square in this direction.  7E                  LD A,(HL)          Find the contents of this square.  60                  LD H,B             The direction being moved is now                                         stored in H, as required.  C1                  POP BC             The number of choices of moves on                                         the stack - B - is recovered.  FE80                CP 80              Is this destination square empty?  20E3      TEST      JR NZ,NXTMRND      If not pick a new direction to                                         examine.  ED537940            LD (SCANSQR),DE    Temporarily store the value of DE  

Note that while we need to temporarily store DE somewhere, we must not stack it, since we are shortly about to use the stack to examine our list. OLD ROM owners should interpret the address (SCANSQR) as 4020.

[Write to 4E70h.]    CDF14D              CALL SQUAREVAL    Check for danger and/or protection                                        at destination square.  

This is necessary because a move into danger is bad, and moving to protect another piece is good. Notice that by design the subroutine SQUAREVAL will not change the value of any register except A. One unfortunate flaw in the subroutine means that moving a king into danger will only generate the value five, rather than seven. Can you see why? Follow the subroutine through if you can't. Finally you should note that SQUAREVAL only requires L to be assigned initially, not HL. This is deliberate.

[Write to 4E73h.]    57        NEWPRI    LD D,A            Negate this quantity, since we do  3A2140              LD A,(INITIAL)    not want to move into danger, and  92                  SUB D             we do want to move to protect  57                  LD D,A            another piece. Add in the original                                        square value and store the result                                        in D.  1E01                LD E,01           The number one is the number of  69                  LD L,C            steps involved in this move.  

We now have D containing the computed priority of this move, and E containing the number of steps in this move.

[Write to 4E7Ch.]    E3                  EX (SP),HL        We now have H containing the                                        priority of the list, and L con-                                        taining the no. of steps for each                                        move on the list.  A7                  AND A  ED52                SBC HL,DE         Compare these two sets of  280D                JR Z,EQUAL        quantities.  19                  ADD HL,DE         Restore HL and the stack-top.  E3                  EX (SP),HL  3013                JR NC,FORGETIT    If computed priority is less, then                                        do nothing.  ED7B7B40            LD SP,(LBASE)     Otherwise begin new list.  0600                LD B,00           Zero items on list so far.  D5                  PUSH DE           Stack the priority and no. of  1802                JR NEWITEM        steps.  19        EQUAL     ADD HL,DE         Restore HL and the stack-top.  E3                  EX (SP),HL  04        NEWITEM   INC B             Increase no. of items in list.  E5                  PUSH HL  

Now H contains the direction moved, and L the low part of the initial square. The top of the stack therefore now looks like this:

+-------------------+-------+--------->  |initial  direction |no. of |priority >  |square   one       |steps  |         >  +-------------------+-------+--------->  |  sp  

This is not quite what we want - we want it to look like this:

+-------+---------+------------------->  |no. of |priority |initial  direction >  |steps  |         |square   one       >  +-------+---------+------------------->  |  sp  

So we now want to swap the first and second bytes at the top of the stack with the third and fourth bytes. We want to do this without altering the position of the stack pointer, and without altering any of the registers. The following will achieve this - follow it through carefully -

[Write to 4E93h.]    33                  INC SP        Move the stack pointer to the initial  33                  INC SP        square (final position).  E3                  EX (SP),HL    Store initial square and direction 1.  3B                  DEC SP        Move the stack pointer back where it  3B                  DEC SP        came from.  E3                  EX (SP),HL    Store the number of steps and priority  

Note that even HL remains unchanged by this method. EVALUATE needs only two more instructions to complete it. These are

[Write to 4E99h.]    ED5B7940  FORGETIT  LD DE,(SCANSQR)    Restore the previous values of D  18B0                JR NXTMRND         and E, and do the same for next                                         direction.  

As it stands the program will not test whether or not a computer's piece has reached the back row (and thus become a king). This is not a programming error, this is quite deliberate. The reason is that this is something I'd like you to do for yourself. Study the way in which the check on a human's piece is made - the low part of the destination address is compared with the low part of the address of the boundary between the back row and the second row - and make a similar test. You should find this a very simple addition to the program.

The EVALUATE routine is now complete. The whole program is now a closed structure - there are no holes in it now, no RET statements temporarily taking the place of subroutines that aren't there. If you now RUN the program (by typing RUN 4) it will actually make moves! Of course it won't do much else, but you should now be able to see how far we've progressed.

Oh - there is of course that deliberate mistake to think about. If you didn't notice it in the listing you probably noticed it by playing it. The problem is that the computer won't jump. As you can imagine this leads to a very poor game on its part.

The mistake is in the line labelled TEST. It currently says JR NZ,NXTMRND, which means that if a square in any particular direction is simply not empty then it will try a different direction. The line should read JR NZ,WHAT, where WHAT is a routine (which we haven't yet written) which is designed to decide whether the destination square contains a human's piece, whether a jump is possible - even whether or not a multiple jump is possible - and to evaluate the priority of whatever it finds.

[Thunor: To fix the deliberate mistake, change the byte at address 4E6Bh to 33h.]

Here is one such subroutine. It is not the only possible one, but a suggestion of one means of doing it. This particular version will cope only with single jumps, not with multiple jumps: The routine begins at 4E9B [4E9F]:

[Write to 4E9Fh.]    ED537940  WHAT      LD (SCANSQR),DE    Temporarily store value of DE.  57                  LD D,A             Store the contents of the square                                         we are now looking at in D.  E67F                AND 7F             Is it a human's piece?  FE27                CP 27  2806                JR Z,FOUND  ED5B7940            LD DE,(SCANSQR)    If not, retrieve the original  1899                JR NXTMRND         value of DE and resume search.  189F                JR NXTMRND         value of DE and resume search.  3E81      FOUND     LD A,81            Assign A with either five or  CB12                RL D               seven depending on whether or not  3F                  CCF                we have found a king.  17                  RLA  17                  RLA  57                  LD D,A             Store this in D.  5C                  LD E,H             Store the current direction in E.  7D                  LD A,L             Find the next square in this  84                  ADD A,H            direction.  6F                  LD L,A  2640                LD H,WKBOARD-low  7E                  LD A,(HL)          Find the contents of this square.  63                  LD H,E             Restore H to its previous value.  FE80                CP 80              Is this square empty?  2807                JR Z,JUMP  ED537940            LD DE,(SCANSQR)    If not, restore the original  C34F4E              JP NXTMRND         value of DE and resume search.  CDF14D    JUMP      CALL SQUAREVAL     Check for danger and/or                                         protection at destination square.  92                  SUB D              Take contents of square into                                         account.  18A2                JR NEWPRI          Check this new priority to see if                                         it's worth stacking.  

[Download available for 16K ZX81 -> chapter15-draughts3.p. That's it for the author supplied code. The author states that to finish it requires that you write the remaining parts yourself! At the moment I don't intend to go any further with the program as I was expecting it to be a complete version. The version here that I am offering for download includes all of the author offered code and the modification to the deliberate mistake. Set RAMTOP to 4A00 (18944) with POKE 16389,74/NEW. Type RUN 4 to play the game. Unfortunately the game crashes when the computer makes a jump. When transcribing, I have a process of checking the code when writing it and then checking again when I've written a sizeable chunk. I've also gone through and checked the hex-code of the program against the listing in appendix six, and so I believe there is a logic error somewhere that I currently don't have the time to track down.]

[Thunor: For the NEW ROM only, there's a detailed program listing with instructions in appendix six.]

As you can see, the principle for finding a single jump is relatively straightforward. With this routine in place the computer will now play an adequate game of draughts, but although the human player is allowed to make multiple jumps, the computer will not. This addition I leave you to write yourself. I will, however give you a couple of hints.

First of all, the registers all have specific uses. All that is, except for A and C. These are as follows:

  • B - The number of choices of move available.
  • D - The priority of the current move.
  • E - The number of steps in the current move.
  • H - The direction being moved this step.
  • L - The low part of the address of the current square (within WKBOARD).

I suggest giving C a use too - it should be used to store which step of a multiple-step move we are currently examining. In other words, on the second step C will be two, on the third step C will be three, and so on. It is fairly easy to preserve the values of all of the registers by making proper use of the stack.

Nesting the subroutines and loops properly, so that the same routine is used to check for a third move as is used to check for a second move, is not as difficult as you might think - it merely requires a bit of positive thinking. It also has the advantage that, in theory, the computer can actually make twelve-fold jumps with no extra programming. The looping is not the biggest problem.

There are two problems which will face you. These are:

  • Having stored C-1 steps of the current move on the stack, how do we store step C (i.e. how do we insert it into the middle of the stack)?
  • Having established that the current move now stands at C steps, and can be increased no more, one of the following must happen: If C is less than E then the current move is abolished; if C is equal to E, the stack is left unchanged; if C is greater than E then the whole list of moves on the stack except the current move is abolished.

Let's take a look at the first problem first. Assuming C-1 steps are stacked, the situation we now have is this:

+---+---------+------------------> - <------+------------------> - <------+  | E |priority |initial dir. dir. >   < dir. |initial dir. dir. >   < dir. |  |   |         |square   1    2   >   < C-1  |square   1    2   >   <  E   |  +---+---------+------------------> - <------+------------------> - <------+  |  sp  

We wish to insert "direction C" between "direction C-1" and the initial square of the second move. The following subroutine will do just that, but follow it through very carefully because its mechanism is quite intricate.

C5        ADDASTEP  PUSH BC       The number of bytes at the top of the  D5                  PUSH DE       stack which need to be shifted down  E5                  PUSH HL       is C plus two, but once BC, DE, and HL  3E08                LD A,08       have been pushed onto the stack the  81                  ADD A,C       actual number is C plus eight.  210000              LD HL,0000  44                  LD B,H  4F                  LD C,A        The number is stored in BC.  39                  ADD HL,SP     HL points to the top of the stack.  54                  LD D,H  5D                  LD E,L  1B                  DEC DE        DE points to one byte below this.  EDB0                LDIR          Part of the stack is moved down.  3B                  DEC SP        The stack pointer is moved also.  E1                  POP HL  7C                  LD A,H  12                  LD (DE),A     The current direction is put in place.  D1                  POP DE  C1                  POP BC        The registers are retrieved.  0C                  INC C         C is increased to indicate that we are                                    now at the next step.  

You'll notice that the sequence LD HL,0000/ADD HL,SP is necessary because there is no such instruction as LD HL,SP (even though LD SP,HL is allowed). LDIR is used to shift the required part of the stack down one byte. The exact number of bytes to be shifted must first be very carefully calculated, and stored in BC in order that LDIR will work properly. Coincidentally LDIR will leave DE finally pointing to just the right address for us to store the current direction. Since HL is at the top of the stack we may remove it, and load the current direction (H) into position, via A, before we remove DE and BC. Thus the stack pointer is still where we want it, and none of the values of any register (except A) have been changed.

The stack now looks like this:

+---+---------+------------------> - <-----------+-------------> - <------+  | E |priority |initial dir. dir. >   < dir. dir. |initial dir. >   < dir. |  |   |         |square   1    2   >   < C-1   C   |square   1   >   <  E   |  +---+---------+------------------> - <-----------+-------------> - <------+  |  sp  

Finally, C is incremented because we are now ready to examine the next step.

The two procedures involved in the second problem may be solved by careful study of the above process. To abolish the current move is simple - DE is popped, the stack pointer is then incremented by the exact number of bytes, and DE is pushed back again. The second procedure, that of abolishing the whole list except for the current move may be achieved by loading HL with the position within the stack of "direction C", DE with the contents of the variable LBASE, and then using LDDR, however, you'll have to do some thinking in order to work out BC (the number of bytes to be moved) and the new position of the stack pointer. If you understand how ADDASTEP works it will not be all that difficult to do.

With this problem to solve, I will leave you. It's not impossible I assure you. Finally, consider the length of this program so far - our addresses still begin with 4E, and we are allowed to go as far as 4FFF (although we need some left over for the screen and the stack). 1K draughts is quite, quite possible. With thought you may even be able to shorten it further.

DOWNLOADING

Although the program is only 1K it is currently stored in the fourth K. To download it into the first K the procedure is this.

Change every address beginning with 4C to the corresponding address which begins 40. Do the same for 4D, changing it to 41, change 4E to 42, and 4F to 43.

Delete all lines of BASIC except the following:

OLD ROM                            NEW ROM  1 RANDOMISE USR(printboard)        1 INPUT A$  2 INPUT A$                         2 RAND USR game  3 RANDOMISE USR(game)  4 GO TO 2                (USE ANY FIVE DIGIT NUMBER FOR NOW)  

Reserve enough space for the machine code using a series of REM statements from line 5 onwards. On the OLD ROM a REM statement with 46 characters after the word REM occupies exactly fifty bytes. On the NEW ROM a REM statement with 44 characters after the word REM occupies fifty bytes. The machine code will eventually overwrite not only the characters after the word REM, but the word REM itself and even the line numbers.

OLD ROM: type POKE 16463,-1  NEW ROM: type POKE 16535,-1  

All of your REMs should disappear from the listing.

Now, using a machine code program, which you should store somewhere in the third K, copy all of the draughts program from address 4C97 onwards, down to 4097 onwards.

OLD ROM: copy the board printing routine to the print immediately after the draughts program proper finishes.

NEW ROM: DO NOT copy the board printing routine at all. Instead, leave it at 4C09, and replace the instruction RET by the following machine code program.

217D40              LD HL,FIRSTLINE    Fool the ROM into thinking that  222940              LD (NXTLIN),HL     the first line of the program is  C30703              JP SAVE            about to be executed, then jump                                         to the SAVE routine.  

Start your cassette recorder up, so that it is recording, not playing, and type as a direct command RAND USR 19487. This should be done in the FAST mode. The program will then do the following tasks:

  • Print the playing board.
  • Specify that line one is about to be executed.
  • SAVE the program, and the current display file (with the board pre-set-up) and the fact that line one is about to be executed.

When you re-load from tape you will be in mid-program, with the first move (yours) about to be made.

The label "printboard" for the OLD ROM refers to the address at which the board printing routine is to be placed. The label "game" refers to the address 16612.

For the OLD ROM, the address WKBOARD should be changed to that of the board printing routine throughout. In this way the same space is effectively used twice. For the NEW ROM, the address WKBOARD should be left unchanged at 403C.

 

 

 

How to Disassemble the ROM

[Or How to Write an Optimised Disassembler for the ZX81]

There are three "levels" at which we may disassemble, each slightly more sophisticated than the previous. The first two levels are not all that satisfactory, but they are very easy to program.

The first "level" we have already achieved - the USR routine HLIST which we saw earlier in the book will do this for us. That is, given an address such as 0808 it will produce an output like this:

0808 57  0809 ED  080A 4B  080B 39 T  080C 40  ...  

and so on. This is not really disassembly, although you can of course look these bytes up in the tables at the back of the book, but it's quite a time consuming task, and you're also very likely to get lost halfway through. The second "level" is not much better, but again is quite easy to program. What I'm talking about is an output something like this:

0808 57  0809 ED4B3940  080D 79  080E FE21  ...  

and so on. As you can see, each instruction has its component bytes listed out to exactly the right length. This produces a very pleasing display, and there is little or no chance of you "getting lost" when actually looking these bytes up in tables. The third "level" is the one we are actually aiming at - the one everybody wants. What we'd really like is an output like this:

0808 LD D,A  0809 LD BC,(4039)  080D LD A,C  080E CP 21  ...  

and so on. This can be quite easy to program - simply make the computer look up the appropriate words from a table instead of doing it ourselves - however this would take up rather a large amount of space just to store the table. Around 4K in fact. The method I will describe to you will allow such a program to fit in just 1K, but be warned: it's rather difficult. There is actually a "fourth level" of disassembly, which I won't even attempt to touch, but you may like to think about. Imagine an output like this:

PRINT     LD D,A            LD BC,(S_POSN)            LD A,C            CP 21            JR Z,EXIT            ...  

As I've said, I'm not even going to touch this one. The only extra it involves is storing yet another table, this time containing all of the labels used. Let's go back a bit now to something relatively simple. Let's consider a slightly improved version of HLIST which reaches the "second level" of disassembly, and works out the length of each instruction before printing it.

All we need is a table containing just two pieces of information for each byte. These are a) the number of bytes in an instruction beginning with this byte, and b) the number of bytes in an instruction beginning with DD or FD followed by this byte. As you know, some confusion may arise over those instructions beginning with CB or ED, but we don't actually need any tables or anything to cope with these provided we remember the following rules:

  • All instructions beginning CB are two bytes in length.
  • All instructions beginning DDCB or FDCB are four bytes in length.
  • All instructions beginning ED are two bytes in length, except for LD BC,(pq), LD DE,(pq), LD SP,(pq), LD (pq),BC, LD (pq),DE, and LD (pq),SP. The byte immediately after ED for these six instructions is 4B, 5B, 7B, 43, 53, or 73. In binary, all of these numbers have the form 01-- -011. No other instructions have this form.
  • There are no instructions beginning DDED or FDED.

Thus we need a table containing a very small amount of information relating to each byte. Firstly, those instructions which do not begin DD, ED, or FD can only be one, two, or three bytes in length. This means that to store the required information we only need two bits. Secondly those instructions which begin DD or FD can only be two, three, or four bytes in length, so ignoring the DD or FD itself this leaves one, two, or three bytes. Again we need only two bits. This makes four bits altogether, and we can thus represent the appropriate lengths for each byte by a single hexadecimal digit. Our program then will make use of the following table, called LENS. It should be stored such that each element of the table has the same high part of its address:

LENS      DEFB      5F 55 55 A5 55 55 55 A5                      AF 55 55 A5 A5 55 55 A5                      AF F5 55 A5 A5 F5 55 A5                      AF F5 99 E5 A5 F5 55 A5                      55 55 55 95 55 55 55 95                      55 55 55 95 55 55 55 95                      55 55 55 95 55 55 55 95                      99 99 99 59 55 55 55 95                      55 55 55 95 55 55 55 95                      55 55 55 95 55 55 55 95                      55 55 55 95 55 55 55 95                      55 55 55 95 55 55 55 95                      55 FF F5 A5 55 FE FF A5                      55 FA F5 A5 55 FA F5 A5                      55 F5 F5 A5 55 F5 FA A5                      55 F5 F5 A5 55 F5 F5 A5  

As you can see, there are sixteen rows, and sixteen hex digits in each row. Those instructions beginning with DD or FD which do not exist, such as DD00, have been simply assigned the appropriate number of bytes as if the DD/FD were not there.

The following program will "disassemble" to a string of bytes of the right length. It assumes that the table LENS exists, and it assumes that a subroutine HPRINT exists which prints the contents of the A register in hexadecimal without corrupting the other registers. This subroutine was in fact given earlier on in the book.

[Thunor: I recommend using chapter11-hexld3d.p so that you can make use of the available HPRINT and address input mechanism. Therefore I've taken the liberty of adding a couple of instructions near the top and updated a couple of relative jumps lower down so that you can achieve this.]

[Thunor: I should point out that this program works if you are using it to view valid code, but it doesn't deal with invalid prefixed instructions. Because of this I chose to build upon the author's work and create something more suitable; my enhanced version is listed here.]

2A954A    LLIST     LD HL,(ADDRESS)   [Thunor: I've added this to enable                                        address selection.]    2B        START     DEC HL            HL is just the address from which  23        NEXT      INC HL            we are disassembling.    22954A              LD (ADDRESS),HL   [Thunor: I've added this so that                                        typing CONT works.]  3E76                LD A,76  D7                  RST 10            Print a newline.  7C                  LD A,H  CDhprint            CALL HPRINT       Print H in hex.  7D                  LD A,L  CDhprint            CALL HPRINT       Print L in hex.  AF                  XOR A  D7                  RST 10            Print a space.  0E00                LD C,00           C is just a flag to let us know                                        whether or not an instruction                                        begins with DD or FD.  7E        BYTE      LD A,(HL)         Obtain the byte to be disassembled.  FEDD                CP DD             Does it begin with either DD or FD?  2804                JR Z,DDFD  FEFD                CP FD  2007                JR NZ,NORM  CDhprint  DDFD      CALL HPRINT       If so, print "DD" or "FD" and look  23                  INC HL            at the next byte.  0C                  INC C             Change the flag C accordingly.  18F0                JR BYTE           Continue with next byte.  FEED      NORM      CP ED             Does the instruction begin ED?  201A                JR NZ,NOTED  CDhprint            CALL HPRINT       If so, print "ED" and look at the  23                  INC HL            next byte.  7E                  LD A,(HL)  E6C3                AND C3            Is it of the binary form 01-- -011?  E6C7                AND C7            Is it of the binary form 01-- -011?  FE43                CP 43  2004                JR NZ,ONE  0603                LD B,03           B counts the number of bytes to be  1802                JR THREE          printed after the byte ED.  0601      ONE       LD B,01    CDhprint  THREE     CALL HPRINT       Print the next B bytes.  7E        THREE     LD A,(HL)  CDhprint            CALL HPRINT       Print the next B bytes.  23                  INC HL            [Thunor: I've rearranged this part  7E                  LD A,(HL)         as the AND C3 test above destroyed  10F9                DJNZ THREE        regA e.g. ED5B9540 became ED43...]    18C2                JR NEXT           Continue with next byte.  18BE                JR START          [Thunor: I've modified this because                                        the INC HL above was skipping over                                        an address - HL was being                                        incremented again at NEXT.]    E5        NOTED     PUSH HL           Temporarily store HL.  CB2F                SRA A             Divide A by two.  F5                  PUSH AF           Store the carry flag.    E67F                AND 7F            [Thunor: I've added this as SRA                                        duplicates bit 7 into bit 6, and so                                        bytes >= 80h wouldn't halve                                        properly!]    C6lens-low          ADD A,LENS-low    Find the required position in the  6F                  LD L,A            table.  26lens-high         LD H,LENS-high  F1                  POP AF            Retrieve the carry flag.  7E                  LD A,(HL)  3804                JR C,DIG2         Use the carry flag to decide on  1F                  RRA               which digit from the table will be  1F                  RRA               used.  1F                  RRA  1F                  RRA  0D        DIG2      DEC C             Use C to decide which two bits  2002                JR NZ,OK          to use.  1F                  RRA  1F                  RRA  E603      OK        AND 03            Put this number in B to use as  47                  LD B,A            a count.  E1                  POP HL            Retrieve the address of the byte to  2B                  DEC HL            be disassembled.  23        NXBYT     INC HL  7E                  LD A,(HL)  CDhprint            CALL HPRINT       Print B bytes in hex.  10F9                DJNZ NXBYT  1899                JR NEXT           Continue with next byte.  

[Download available for 16K ZX81 -> chapter16-lenslist.p. Set RAMTOP to 4A00 (18944) with POKE 16389,74/NEW. Type RUN to use LENS LIST. Addresses used: 4A82 to 4B77 is occupied by HEXLD3D, LENS is 4A00, HPRINT is 4A82 and LLIST is 4B78 (19320). To test it, list 4B78 and match the output to the listing above. For a more thorough test, list 4A82 and match it with HEXLD3 in appendix one, remembering that it's been relocated from 4082 to 4A82.]

Now we ascend to the "third level" - REAL disassembly in other words. However, I am not going to write the program for you this time round - you'll have to do it by yourself. I will explain precisely what it is you have to do in order to make a 1K disassembler, but the actual program itself must be your creation.

DISASSEMBLING THE ROM

The following is an algorithm which will enable you to disassemble the hex codes into assembly, that is to change, for example, 69 to LD L,C, or from CB7E to BIT 7,(HL). One way would be to list a vast table - such as I have included in the appendices - but while alright for human beings it lacks the elegance of a well thought out computer program. The data alone would occupy around 4K. This algorithm will enable you to write your own machine language program occupying significantly less - two or even 1K all told depending on how efficient your program is.

In this algorithm, the following conventions will be used:

c(0) means NZ    n(0) means 0     q(0) means BC  c(1) means Z     n(1) means 1     q(1) means DE  c(2) means NC    n(2) means 2     q(2) means Y  c(3) means C     n(3) means 3     q(3) means AF  c(4) means PO    n(4) means 4  c(5) means PE    n(5) means 5  c(6) means P     n(6) means 6  c(7) means M     n(7) means 7    r(0) means B     s(0) means BC    x(0) means ADD A,  r(1) means C     s(1) means DE    x(1) means ADC A,  r(2) means D     s(2) means Y     x(2) means SUB  r(3) means E     s(3) means SP    x(3) means SBC A,  r(4) means H                      x(4) means AND  r(5) means L                      x(5) means XOR  r(6) means X                      x(6) means OR  r(7) means A                      x(7) means CP  

Define two variables, CLASS and INDEX, and initially let both of them equal zero.

Write the byte being disassembled in binary, and split it into three parts; F, G, and H. F consists of bits 7 and 6, G of bits 5, 4, and 3, and H of bits 2, 1, and 0. Thus to disassemble the byte 69 (binary 0110 1001) just split it into three parts thus: 01/010/001. In this particular case F is one, G is five, and H is one.

Next, split G into two parts; J and K; with J consisting of bits 2 and 1, and K just bit 0. If G then were binary 101 as above then split it like this: 10/1. In this case we would define J to be two, and K to be one.

Set aside an area of memory called DIS. This is to contain a STRING of unknown length. How you store this string is up to you. There are two different methods you could use - either terminate the data with an end-of-data character (any character will do, FF is as good as any), or begin the area DIS with one byte representing the number of characters of data there are in the string (you only need one byte since DIS will never be more than 255 characters in length). DIS should initially be an empty string, (i.e. containing no characters at all).

The algorithm begins here.....

[Thunor: I have modified the prefixed instruction  classification code at the start of the algorithm and  moved the check for 76 (HALT) down to the F = 1 condition.    Although this algorithm recognises and classifies the many  different instruction types, it doesn't filter out invalid  prefixed instructions. The easiest way I know to validate  prefixed instructions is to use one bit to represent DDxx  and FDxx, another for EDxx with CBxx, DDCBddxx and FDCBddxx  being validated in code; you will only need a 64 byte table  for all the bits (2 * 256 / 8). The validation should be  done before breaking down the byte to be disassembled into  its FGHJK components.    Following is a useful table showing the instruction types  handled by all possible values of CLASS and INDEX:    +-------+------------------------------+  |       |           INDEX              |  +-------+--------+----------+----------+  | CLASS |  0 HL  |   1 IX   |   2 IY   |  +-------+--------+----------+----------+  |   0   | xx     | DDxx     | FDxx     |  |   1   | CBxx   | DDCBddxx | FDCBddxx |  |   2   | EDxx   |    --    |    --    |  +-------+--------+----------+----------+    If you require the byte count for the instruction before  the data byte(s) are added:  Let count = 1  If INDEX > 0 then let count = count + 1  If CLASS > 0 then let count = count + 1  Then when computing the final output, if you convert Vs or  add displacements then you should increment count for each  byte of data.    Y, X and V are place holders and are updated at the end of  the algorithm. The problem with this is that "X" is used  within some instructions and so I recommend that you use  something else such as inverse equivalents. Additionally  you might also want to consider representing VV with an  inverse W or maybe WV to identify the high and low bytes.    At the end of the algorithm before computing the final  output there's a space saving method of constructing the  strings for 16 different instructions after ED. This does  though present a problem with a missing "U" in OUTI and  OUTD and so you'll want to check for H=3 and (G=4 or G=5)  to identify and provide a solution to this.    At the start, when it says that you should start again, it  means read the next byte, split it into FGHJK and then  return to the top of the algorithm.    Comment end.]    If CLASS equals zero then the following applies:  1) If the byte is 76 then complete disassembled instruction is HALT.  2) If the byte is CB then let CLASS equal one and start again.  3) If the byte is ED then let CLASS equal two and start again.  4) If the byte is DD then let INDEX equal one and start again.  5) If the byte is FD then let INDEX equal two and start again.  6) If F equals zero then....  [If the byte is CB then...]      [If INDEX <> zero then point past displacement.]      [Let CLASS equal one and start again.]  [If INDEX equals zero then...]      [If the byte is ED then let CLASS equal two and start again.]      [If the byte is DD then let INDEX equal one and start again.]      [If the byte is FD then let INDEX equal two and start again.]  [If F equals zero then....]      If H equals zero then....          If G greater than three then let DIS equal JR c(G-4),V.          If G less than four choose the Gth item in this list:          NOP/EX AF,AF'/DJNZ V/JR V      If H equals one then...          If K is zero then let DIS equal LD s(J),VV          If K is one then let DIS equal ADD Y,s(J)      If H equals two then...          Let DIS equal LD plus the Gth item in this list:          (BC),A/A,(BC)/(DE),A/A,(DE)/(VV),Y/Y,(VV)/(VV),A/A,(VV).      If H equals three then...          If K is zero then let DIS equal INC s(J)          If K is one then let DIS equal DEC s(J)      If H equals four then let DIS equal INC r(G)      If H equals five then let DIS equal DEC r(G)      If H equals six then let DIS equal LD r(G),V      If H equals seven then choose the Gth item from this list:      RLCA/RRCA/RLA/RRA/DAA/CPL/SCF/CCF.  If F equals one then let DIS equal LD r(G),r(H).  [If F equals one then...]      [If the byte = 76 then let DIS = HALT.]      [If the byte <> 76 then let DIS equal LD r(G),r(H).]  If F equals two then let DIS equal x(G) r(H).  If F equals three then....               If H equals 0 then let DIS equal RET c(G)      If H equals one then...          If K is zero then let DIS equal POP q(J)          If K is one then choose the Jth item from this list:          RET/EXX/JP (Y)/LD SP,Y.      If H equals two then let DIS equal JP c(G),VV      If H equals three then choose the Gth item from this list:      JP VV/-/OUT (V),A/IN A,(V)/EX (SP),Y/EX DE,HL/DI/EI.      If H equals four then let DIS equal CALL c(G),VV      If H equals five then...          If K is zero then let DIS equal PUSH q(J).          If K is one then let DIS equal CALL VV.      If H equals six then let DIS equal x(G) V.      If H equals seven then let DIS equal RST plus the Gth item      in this list: 00/08/10/18/20/28/30/38.    If CLASS equals one then the following applies:  If F equals zero then choose the Gth item from this list:  RLC/RRC/RL/RR/SLA/SRA/-/SRL and then add r(H).  If F equals one then let DIS equal BIT n(G),r(H).  If F equals two then let DIS equal RES n(G),r(H).  If F equals three then let DIS equal SET n(G),r(H).    If CLASS equals two then the following applies:  F cannot possibly equal zero.  If F equals one then....      If H equals zero then let DIS equal IN r(G),(C).      If H equals one then let DIS equal OUT (C),r(G).      If H equals two then...          If K equals zero then let DIS equal SBC HL,s(J).          If K equals one then let DIS equal ADC HL,s(J).      If H equals three then...          If K equals zero then let DIS equal LD (VV),s(J).          If K equals one then let DIS equal LD s(J),(VV).      If H equals four then let DIS equal NEG.      If H equals five then...          If K equals zero then let DIS equal RETN.          If K equals one then let DIS equal RETI.      If H equals six then choose the Gth item from this list:      IM 0/-/IM 1/IM 2/-/-/-/-.      If H equals seven then choose the Gth item from this list:      LD I,A/LD R,A/LD A,I/LD A,R/RRD/RLD/-/-.  If F equals two then choose the Hth item from this list: LD/CP/IN/OT/  -/-/-/- and then add the Gth item from this list: I/D/IR/DR/-/-/-/-.  -/-/-/- and then add the Gth item from this list: -/-/-/-/I/D/IR/DR.  F cannot possibly be three.    To compute the final output:  If INDEX equals zero replace every Y by HL.  If INDEX equals one replace every Y by IX.  If INDEX equals two replace every Y by IY.    If INDEX equals zero replace every X by (HL).  If INDEX equals one replace every X by (IX+d) where d is defined by      the next byte but one after the byte DD.  If INDEX equals two replace every X by (IY+d) where d is defined by      the next byte but one after the byte FD.  (This does not apply if the X is preceded by I.)    Replace every V by the next byte in sequence (of those being  disassembled).    DIS now contains the correctly disassembled instruction. This should  now be printed to the screen.  

[Download available for 16K ZX81 -> sif-disasm.p. There's also a complete listing available which you can easily compare to the algorithm above.]

It is possible to write a machine language program which disassembles things by using this algorithm. In fact it is possible to write such a program in just 1K. Surprising as this may sound I should add that although it is possible, the program itself is rather complicated, and involves a completely new programming technique.

What I will do is to not actually write the program for you, but to give you hints and suggestions as to how it may be done. The program revolves around eight different subroutines, which are linked together by one MASTER subroutine which calls them all up in any required order. This is achieved as follows.

Somewhere in the program there should be a table called SUBTAB which contains eight different addresses - these are the addresses of the eight subroutines which control the program. The register-pair HL' (note the dash) will be pointing to a sequence of data which tells the MASTER subroutine which order it must call the others in. The data in this sequence is terminated by an item in which bit 7 is one. The data consists simply of numbers zero to seven. Zero calls subroutine zero, one calls subroutine one, and so on. Thus this number zero to seven determines exactly which subroutine the MASTER routine is to call.

So any item of data in this sequence looks, in binary, like this: 0--- -nnn for most items, or 1--- -nnn for the last item (the part written nnn means the appropriate number zero to seven as described). Now some of these eight subroutines will need to be supplied with DATA, which by coincidence will also need to be a number between zero and seven - if this number in binary is ddd then it makes sense to save space by storing this number amongst some of the unused bits of the subroutine-call, thus making it look, in binary, like this: 0-dd dnnn or 1-dd dnnn. We have now made use of every bit except bit 6. This isn't needed, so for [the] sake of argument let's always make it zero. Any item of data in the sequence can then be 00dd dnnn, but the last byte must be 10dd dnnn.

I hope that didn't confuse you. To make things clear, suppose HL' points to an address at which is stored the sequence of data 00 01 22 83. This means that first of all subroutine zero is to be called, then subroutine one, then subroutine two (which will use the data binary 100 somewhere), then finally subroutine three. I say "finally" because bit 7 is set which means we are finished.

The master subroutine which will achieve this is as follows:

D9        MASTER    EXX  7E                  LD A,(HL)         Find byte of data, and increment  23                  INC HL            pointer.  D9                  EXX  5F                  LD E,A            Store this byte, in case bits 5, 4,                                        and 3 contain data to be used in                                        the appropriate subroutine.  E607                AND 07            Isolate bits 2, 1, and 0.  17                  RLA               Multiply by two.  4F                  LD C,A            Store this number in the BC  0600                LD B,00           register pair.  21 return           LD HL,RETURN      Specify the return address from  E5                  PUSH HL           each of the eight subroutines.  21 mastrads         LD HL,MASTRADS    Point HL to the start of the table                                        which stores the eight subroutine                                        call addresses.  09                  ADD HL,BC         Point HL to the required address.  4E                  LD C,(HL)         Store this address in the BC  23                  INC HL            register pair.  46                  LD B,(HL)  C5                  PUSH BC           Call this subroutine.  C9                  RET  7B        RETURN    LD A,E            If bit 7 was not zero then continue  17                  RLA               with the next byte of data.  30E8                JR NC,MASTER  

You can learn a lot from studying this MASTER-SUBROUTINE. Can you see how the appropriate subroutine (one of eight) is called? First of all the label RETURN is pushed onto the stack. This means that if each of the eight routines ends with a RET instruction then control will jump to the label RETURN - just as if the subroutine had been accessed normally. To call the subroutine itself, the address of which was in the register-pair BC, we used PUSH BC followed by RET. Think carefully about how this works. The required address is pushed onto the stack, above the address RETURN. Then a RET instruction is executed. RET has the effect of popping the first number from the stack (the subroutine address) and jumping to that address. The first address left on the stack is now the address RETURN, which enables control to return correctly. All of this is necessary because there is no such instruction as CALL (BC) - in BASIC the statement GOSUB VARIABLE is allowed, but not in machine code. Another way we could have achieved the same as PUSH BC/RET is by using the sequence LD H,B/LD L,C/JP (HL). Can you see why this does the same thing?

You may be wondering how the appropriate address came to be in HL' in the first place. There are two means by which this will be determined. Note that all of the alternative registers have specific jobs. These are:

BC'       The address of the byte to be disassembled.  D'        The variable of INDEX.  E'        The variable CLASS.  HL'       Points to subroutine data.  

The byte to be disassembled is located and stored in the D register by the means EXX/LD A,(BC)/INC BC/EXX/LD D,A. From this the quantities I called F, G, and H may later be discovered. Somewhere in the program there should be a table called TABLE containing twelve different addresses. HL' is simply read from this table. The twelve addresses correspond to the cases CLASS equals zero and F equals 0, 1, 2, or 3; CLASS equals one and F equals 0, 1, 2, or 3; and CLASS equals two and F equals 0, 1, 2, or 3.

The other way in which HL' may be determined is if subroutine zero is called. Subroutine zero is called by the data-byte 00. This will be immediately followed by eight different addresses corresponding to the cases H equals zero, up to H equals seven. Subroutine zero has the task of locating the appropriate address from the list and storing it in the register-pair HL'.

One subroutine you will need (but not one of the eight central ones), is a subroutine to add a single character to the end of the string DIS. Using the convention that the string begins at address DIS and is terminated by the byte FF, the string may be emptied by the sequence LD HL,DIS/LD (HL),FF. To add a character (held in the A register) the subroutine is:

C5        ADDDIS    PUSH BC      Store the registers BC and HL so  E5                  PUSH HL      that they won't be altered by the                                   subroutine.  0601                LD B,01      This is so that CPIR won't stop                                   because of BC.  21dis               LD HL,DIS    Find the start of the string.  F5                  PUSH AF      Temporarily stack A.  3EFF                LD A,FF  EDB1                CPIR         Find the end of the string.  77                  LD (HL),A    Insert a new end-of-string marker.  2B                  DEC HL  F1                  POP AF       Retrieve A.  77                  LD (HL),A    Add this character.  E1                  POP HL       Retrieve the remaining registers.  C1                  POP BC  C9                  RET          End of subroutine.  

The eight subroutines you will need for this disassembly program are as follows:

SUBROUTINE 0 - SPLIT

This is the subroutine called by the byte 00. It is always the first subroutine called, if it is used at all. The byte 00 should be followed [by] eight new addresses within the disassembler program. Located at these addresses are eight different sequences of data, which correspond to the cases H equals zero, H equals one, and so on up to H equals seven. One of these sequences is selected (according to H) and the data used to decide which of the eight subroutines should then be used.

SUBROUTINE 1 - LITERAL

The byte 01 (or 81 if it is the last subroutine-call in sequence) is followed by a series of characters, such as N O and P, which represent part or all of the disassembled instruction. The last character should have one of the unused bits (6 or 7) set, to indicate the fact that it is the last character. The subroutine should use one bit of data, with the meaning that if it is called by the byte 09 (or 89) then the literal data following should have a space inserted after the last character. This literal data is to be added to the end of the data storage area called DIS.

SUBROUTINE 2 - LIST-G

Means select the Gth item from the following list. The subroutine needs data to specify how many items there are in the following list. If there are four items the data 011 (3) is required, if there are eight items, the data 111 (7) is required, and so on, the data always being one less than the number of items in the list. For example the byte 3A (in binary 0/0/111/010 - meaning call subroutine 2 and provide it with the data 111) means select the Gth item from the following list of eight. The list could, for instance, be R, L, C, inverse A, R, R, C, inverse A, R, L, inverse A, R, R, inverse A, D, A, inverse A, C, P, inverse L, S, C, inverse F, C, C, inverse F. I've used 'inverse' to indicate the last character in an individual item. You don't have to do this - you can use any means you choose as long as it works. Thus if G (that is bits 5, 4, and 3 of the instruction being disassembled) were 5, the literal DAA would be added to the end of DIS. The next byte to be interpreted as data will be the byte after the inverse F.

SUBROUTINE 3 - LIST-H

Means select the Hth item in the following list. Its explanation is exactly the same as that of subroutine 2.

SUBROUTINE 4 - SELECT-G

Again, three bits of data are required. Interpret as follows. If the data is 000 select r(G), if the data is 001 select s(G), if the data is 010 select q(G), if the data is 011 select n(G), if the data is 100 select c(G), and if the data is 110 select x(G). The item selected is to be added to the end of DIS.

SUBROUTINE 5 - SELECT-H

As subroutine 4, except that H is used instead of G.

SUBROUTINE 6 - SKIP

Resets bit 5 of E (the data-byte), and if the previous value of bit 5 was one, skips over n bytes of data. The number n is determined by the immediately following byte. If bit 5 was zero this immediately following byte (which is only there to specify n) is ignored, and the next byte after is then interpreted as the next item of data.

SUBROUTINE 7 - K-SKIP

Replace bit 3 of E by bit 4, replace bit 4 by bit 5, and reset bit 5. Effectively this is the same as LET G equal J. Then if the previous value of bit 3 was one, n bytes are skipped over, as in subroutine six. This subroutine can be interpreted as IF K equals zero THEN.... otherwise IF K equals one then....

                        +-----------+        +^^^^^^^^^^^+  Human operator -------->|           |BC'     |           |                          +-----------+        +-----------+          address of byte to be | disassembled |           |                                |              +-----------+                                +------------->|           |---+ points to                                               +-----------+   | an address                                               |           |   | in memory                  the byte to be disassembled  +vvvvvvvvvvv+   |                          +-----------+                        |                        D |           |<-----------------------+     +-----------+        +-----------+     |CLASS      |--------------+   |          +^^^^^^^^^^^+     +-----------+              |   |          |           |     +-----------+              |   |          +-----------+     |TABLESTART |----------+   |   |          |           |     +-----------+          V   V   V          +-----------+                          +-----------+        |           |         address in table |           |        +-----------+                          +-----------+        |           |                                |              +-----------+                                +------------->|           |---+ points to                                               +-----------+   | an address                                               |           |-+ | in memory                                               +-----------+ | |                                               |           | | |                                               +vvvvvvvvvvv+ | |                                                             | |                          +-----------+<---------------------+ |                      HL' |           |                        |                          +-----------+<-----------------------+   the address of the start of  |          the disassembly data  |              +^^^^^^^^^^^+                                |              |           |                                |              +-----------+                                +------------->|           |---+ points to                                               +-----------+   | an address                                               |           |   | in memory                                               +vvvvvvvvvvv+   |                          +-----------+                        |                        E |           |<-----------------------+                          +-----------+     +-----------+ the data itself  |          +^^^^^^^^^^^+     |SUBTABSTART|----------+       |          |           |     +-----------+          |       |          +-----------+                            V       V          |           |    address in subroutine +-----------+        +-----------+                    table |           |        |           |                          +-----------+        +-----------+                                |              |           |                                |              +-----------+                                +------------->|           |---+ points to                                               +-----------+   | an address                                               |           |-+ | in memory                                               +vvvvvvvvvvv+ | |                                                             | |                          +-----------+<---------------------+ |  subroutine call address |           |                        |                          +-----------+<-----------------------+                         / | | | | | | \                        /  | | | | | |  \                       V   V V V V V V   V  

With these eight subroutines, which you will have to write yourself, you can disassemble every instruction. I will give you an example. Suppose CLASS is zero, and F is three. The first byte it has to interpret should be 00. This alters the value of HL' according to the quantity H, that is, bits 2, 1, and 0 of the byte being disassembled. Suppose now that H is one. HL' should now be pointing to the following sequence of data, listed here along with its meaning.

data                binary       meaning  07 05               0000 0111    KSKIP 5  09 35 34 B5         0000 1001    LITERAL POP (space)  94                  1001 0100    SELECT-G.q (EXIT)  9A                  1001 1010    LIST-G.4 (EXIT)  37 2A B9                          RET  2A 3D BD                          EXX  2F 35 00 16 3E 91                 JP (Y)  31 29 00 38 35 1A BE              LD SP,Y  

To represent strings of data here you can see I've used just the character codes, with the final character inversed to show that it is the last character. In other words EXX is written as 2A 3D BD rather than just 2A 3D 3D. It is of course very important to know where one string ends and the next begins.

If you follow through which subroutines have been called by the data and what they are supposed to do you'll see that in a total of only twenty-seven bytes we have said IF K equals zero then LET DIS equal POP q(J), IF K equals one then LET DIS equal the Jth item from this list: RET/EXX/JP (Y)/LD SP,Y. If this procedure is continued for every instruction, following the algorithm I gave earlier in the chapter, you'll find that the data required for disassembly is now significantly LESS than 1K.

The entire disassembly program consists of initialising the variables CLASS and INDEX, assigning BC' (usually input by the human operator), finding the address HL' from tables, and then going into the master-routine. On exiting this it must then replace all V's, X's and Y's as defined earlier in this chapter, and then PRINT the result computed and go on to the next byte to be disassembled and treat it in the same way. The rest of the program consists of the eight subroutines, the table of addresses, and the data required for disassembly. The whole of this will occupy rather less than 1K.

However simple, or difficult, I may have made this program sound, you will undoubtedly find writing it a challenge. The vast majority of the program is data, and each address in every table must point to exactly the right byte. If you get any of it wrong it will be very difficult to trace.

You can improve the program too. I haven't used bit 6 of the data - you may be able to think of a use for it, for example it could indicate that a comma needs to be inserted, the choice is yours.

Like draughts, this program is so vast that even though the machine code listing itself will fit into 1K, you will need more than 1K in order for the machine code to be put there. Any editing program, BASIC or machine code, will take you above the 1K.

Good luck.

 

 

 

The Arithmetic Subroutines

ARITHMETIC SUBROUTINES

This chapter is divided into two sections - one for the OLD, and one for the NEW ROM. We'll tackle the OLD ROM first because it's easier.

[OLD ROM ARITHMETIC]

Numbers are represented in two bytes, and as such it is possible to store them in register pairs BC, DE, and HL. First of all we shall take a look at the five major arithmetic routines.

1) Addition. The address to call is 0D3E, or more intelligibly, CALL ADD. The subroutine adds together the number stored in DE and the number stored in HL. The result is then placed in HL. This may be demonstrated by the following program:

113900    ADDDEMO   LD DE,0039  211100              LD HL,0011  CD3E0D              CALL ADD  C9                  RET  

Here DE is loaded with the number fifty-seven, and HL with seventeen. On return to BASIC the result stored in HL should be fifty-seven plus seventeen, so the command PRINT USR(adddemo) should generate the number seventy-four.

2) Subtraction. Just the same - DE is subtracted from HL and the result stored in HL. The address is 0D39. Thus to prove it:

113900    SUBDEMO   LD DE,0039  211100              LD HL,0011  CD390D              CALL SUB  C9                  RET  

3) Multiplication. Up until now we have ignored multiplication completely, since there is no simple instruction which will multiply two numbers together. However, thanks to Uncle C, the ROM will do it for us. Simply CALL MULT, which is stored at address 0D44, and as if by magic DE will be multiplied by HL, the result as usual being stored in HL. Watch out for what happens to BC and DE though! They're not unaltered.

4) Division. As you'd by now expect, the instruction CALL DIV will divide HL by DE (ignoring any remainder of course, since we are dealing in integers). The address of DIV is 0D90.

5) Powers. Is raising one number to the power of another going to be any more difficult? No of course not. With elegant simplicity the instruction CALL POWER (at 0D0C [0D70]) will do just that, raising HL to the power of DE, and putting the answer away in HL, using repeated multiplication to compute the answer.

One very important function is the RANDOM NUMBER GENERATOR. This is held at location 0BED. To generate a random number between one and six (say to simulate the roll of a die), simply load HL with six and CALL RND. This is of course the same thing as RND(6). The number in the brackets should be placed in HL, and the final answer will end up in HL.

See if you can work out what this program does. What we're interested in is the number that it returns to BASIC.

211400    START     LD HL,0014  CDED0B              CALL RND  110A00              LD DE,000A  CD440D              CALL MULT  116400              LD DE,0064  19                  ADD HL,DE  C9                  RET  

Let's see if you got it right. HL is loaded with 14 and RND is called, so HL is replaced by a new value, RND(20) (note that 14 (hex) is 20 (dec)). 0A is stored in DE, and the two are then multiplied together. We then have 10*RND(20). Finally 64 (hex) is added, giving 10*RND(20)+100.

We could use this routine in a games program. Suppose we needed to jump to a random destination. We could use the by now famous Tim Hartnell method of GOTO 10*RND(20)+100. Alternatively, if the above machine code were in a REM statement, say at address 16427, we could instead simply say GOTO USR(16427). This would do exactly the same job, except a little bit faster.

We'll leave the OLD ROM now, and turn to the rather more complex field of arithmetic on the NEW ROM.

NEW ROM ARITHMETIC

The first and most important point to note is that NEW ROM numbers are stored as five bytes, not two, and so they can't fit into a register-pair as they stand. Nor are the numbers in simple form, for while it is true that zero is, as you'd expect, 00 00 00 00 00, it is not true that one is 00 00 00 00 01! In fact one is represented by 81 00 00 00 00. Here is a list of the Sinclair representaion of the first few integers.

Decimal   Sinclair Form  0         00 00 00 00 00  1         81 00 00 00 00  2         82 00 00 00 00  3         82 40 00 00 00  4         83 00 00 00 00  5         83 20 00 00 00  6         83 40 00 00 00  7         83 60 00 00 00  8         84 00 00 00 00  9         84 10 00 00 00  10        84 20 00 00 00      ...  

and so on. There is a kind of pattern, but it's not instantly recognisable. Take a look at the negative numbers:

Decimal   Sinclair Form  -1        81 80 00 00 00  -2        82 80 00 00 00  -3        82 C0 00 00 00  -4        83 80 00 00 00  -5        83 A0 00 00 00  -6        83 C0 00 00 00  -7        83 E0 00 00 00  -8        84 80 00 00 00  -9        84 90 00 00 00  -10       84 A0 00 00 00  

As you can see, you can instantly change a number from positive to negative just by adding 80 to the second byte. This doesn't apply to zero by the way - zero is represented uniquely to help speed the ROM up a little.

Knowing how the Sinclair Form is built up will slightly help your understanding of the ROM, so I will give here a brief explanation of how to turn decimal numbers into Sinclair numbers. It only takes a few simple steps.

STEP ONE: If the number is zero, then its Sinclair representation is 00 00 00 00 00.

STEP TWO: Ignoring the sign of the number, write it in binary (but without any leading zeroes). For example:

  7        111  -10       1010   -4.25     100.01    0.325      0.011    0.375      0.011  

Notice that the binary form has a BINARY point, not a DECIMAL point! 100.01 means one 4 plus no 2's plus no 1's (here we reach the binary point) plus no halves plus one quarter. The next column would have been an eighth.

STEP THREE is to work out a quantity called the EXPONENT. This is done as follows:

  • If the part of the number to the left of the binary point is not zero then the exponent is the number of digits to the left of the point.
  • If the part of the number to the left of the point is zero and the first digit after the point is one then the exponent is zero.
  • If the part of the number to the left of the point is zero and the first digit after the point is zero, then count the number of zeroes between the point and the first 1 - the exponent is minus this number.

The first byte of the Sinclair representation is 80 plus this exponent.

Decimal   Binary    Exponent  Sinclair Form    7        111      3         83  -10       1010      4         84   -4.25     100.01   3         83    0.325      0.011 -1         7F    0.375      0.011 -1         7F  

STEP FOUR: Now we can ignore the binary point altogether - that is what the exponent is for - to tell the computer where the point is supposed to go. So ignoring the point, write the binary form starting with the first 1 and then add sufficient zeroes to the right to make the whole thing thirty-two binary (bits) in length.

  7       1110 0000 0000 0000 0000 0000 0000 0000  -10       1010 0000 0000 0000 0000 0000 0000 0000   -4.25    1000 1000 0000 0000 0000 0000 0000 0000    0.325   1100 0000 0000 0000 0000 0000 0000 0000    0.375   1100 0000 0000 0000 0000 0000 0000 0000  

STEP FIVE: It is here that we remember the sign of the original number. If the original number was negative then do nothing. If the original number was positive then replace the first one by a zero. Thus:

  7       0110 0000 0000 0000 0000 0000 0000 0000  -10       1010 0000 0000 0000 0000 0000 0000 0000   -4.25    1000 1000 0000 0000 0000 0000 0000 0000    0.325   0100 0000 0000 0000 0000 0000 0000 0000    0.375   0100 0000 0000 0000 0000 0000 0000 0000  

STEP SIX - Now just change these numbers straight into hex, like so, making sure you remember to put the exponent byte at the start:

  7       83 60 00 00 00  -10       84 A0 00 00 00   -4.25    83 88 00 00 00    0.325   7F 40 00 00 00    0.375   7F 40 00 00 00  

This is the form in which the ROM will be working. The largest exponent you may have is FF, so the largest positive number that can be stored is FF 7F FF FF FF. This turns out to be 1.7014118E38 (if you can't understand the "E" notation the E means "with the decimal point shifted (in the above case) 38 places to the right"[)]. In other words the number 170,141,180,000,000,000,000,000,000,000,000,000,000 which is a pretty vast quantity. It can still only store ten decimal places accurately though. The smallest positive number you can have (apart from zero) is 01 00 00 00 00, which happens to represent 2.9387359E-39. To you and me that's 0.000,000,000,000,000,000,000,000,000,000,000,000,002,938,735,9 which I'd say was pretty microscopic.

You can check all of this with the following BASIC program.

10 LET A=0  20 LET B=PEEK 16400+256*PEEK 16401  30 FOR I=1 TO 5  40 INPUT A$  50 POKE B+I,16*CODE A$+CODE A$(2)-476  60 NEXT I  70 PRINT A  

[Download available for 16K ZX81 -> chapter17-floatchk. I have heavily modified this downloadable version so that not only can you enter Sinclair Form numbers in one go, you can also convert from decimal to Sinclair Form too.]

The program sets up a variable A, and then overwrites its previous contents by POKEing into the variables area, one byte at a time (that's a letter I in line 50, not a number 1). If you run it and enter "82"/"40"/"00"/"00"/"00" (where / means newline) you'll find the number three printed. And so on.

An interesting little quirk is that if you input "00"/"80"/"00"/"00"/"00" (in theory this is minus-zero) the machine actually prints -C.6E-56 ! The letter C in mid-number, and an exponent of -56! Don't panic! This doesn't really happen in the ROM. We made it happen by POKEing something that doesn't make sense. The ROM does behave slightly more sensibly than human beings.

HOW TO USE FLOATING POINT NUMBERS PROPERLY

Having seen that a five byte number is too big to store in the registers, the next question is undoubtedly "Well where does it store them then?". Answer - it stores them in an area of RAM called the CALCULATOR STACK, which works very much like the ordinary stack except for two points: 1) It can store both floating point numbers and strings, and 2) it is the right way up, not upside down like the machine stack.

To push a number stored in the BC register onto the calculator stack all you need to do is call up a subroutine in the ROM. CALL STACKBC, as I've called it, will change BC into five byte form as described above and then push this number onto the top of the calculator stack. You can do the same for a number stored in A (i.e. a number between 0 and 255) by calling STACKA. The addresses to call are: 1519 151D (STACKA) and 151C 1520 (STACKBC).

CD1915              CALL STACKA  CD1D15              CALL STACKA         CD1C15              CALL STACKBC  CD2015              CALL STACKBC         

Incidentally the first two instructions in the STACKA routine are LD C,A and LD B,00. It then leaps straight into STACKBC!

Conversely, to retrieve a number from the calculator stack we can CALL UNSTACK (address 0EA7), which removes a number from the calculator stack and stores it in the BC register.

[Thunor: 0EA7 is the 'FIND INTEGER' (->) subroutine which does actually call the 'FLOATING-POINT TO BC' (->) subroutine at 158A but it also quits to BASIC with an error code if there's a problem. Now this behaviour is not what you'd normally expect in machine code programs as they should be in control of these situations, and so I think that CALL UNSTACK should really be calling 158A directly and in fact it works because I've tried it in the 6 * 7 multiplication test below. I suggest that when you see CALL UNSTACK at 0EA7 you should bare in mind that calling 'FLOATING-POINT TO BC' at 158A might be a better idea.]

Arithmetic is quite straightforward. The addresses are:

ADD       1754      addition  ADD       1755      addition  SUB       174B      subtraction  SUB       174C      subtraction  MULT      17C5      multiplication  MULT      17C6      multiplication  DIV       1881      division  DIV       1882      division  

They work like this: The five-byte number stored at an address specified by HL (this means the number is stored in locations (HL), (HL)+1, (HL)+2, (HL)+3, and (HL)+4) is added to, multiplied by, divided by, or has a second number subtracted from it. The second number is stored at an address specified by DE. After the calculation the result is stored in the five bytes beginning at address HL.

To multiply together the two numbers at the top of the calculator stack one method would be as follows:

2A1C40              LD HL,(STKEND)  11FBFF              LD DE,FFFB  19                  ADD HL,DE  E5                  PUSH HL  221C40              LD (STKEND),HL  19                  ADD HL,DE  D1                  POP DE  CDC517              CALL MULT  CDC617              CALL MULT  

Can you follow exactly what is going on? HL is loaded with the contents of the system variable STKEND - which gives the address of the first byte after the end of the calculator stack. DE is loaded with minus five, thus HL is decreased by five. This new value is loaded back into STKEND because we start off with two items on the stack and want to end up with only one. This is the address of one of the numbers to be multiplied. If you follow the listing through carefully you'll see that DE ends up with this value. First though HL is decreased by five again, to find the start of the other number to be multiplied.

To check that it really does work, run this program.

3E06      START     LD A,06  CD1915              CALL STACKA  CD1D15              CALL STACKA  3E07                LD A,07  CD1915              CALL STACKA  CD1D15              CALL STACKA  2A1C40              LD HL,(STKEND)  11FBFF              LD DE,FFFB  19                  ADD HL,DE  E5                  PUSH HL  221C40              LD (STKEND),HL  19                  ADD HL,DE  D1                  POP DE  CDC517              CALL MULT  CDC617              CALL MULT  CDA70E              CALL UNSTACK  C9                  RET  

Run it by typing PRINT USR start. What do you get?

But surely there must be easier ways to multiply six by seven. After all, the above program does look very complicated, and not something you'd easily remember. Well it's here that we really do start making full use of the ROM. The following program does exactly the same job, and I shall shortly explain why:

3E06      START     LD A,06  CD1915              CALL STACKA  CD1D15              CALL STACKA  3E07                LD A,07  CD1915              CALL STACKA  CD1D15              CALL STACKA  EF                  RST 28  04                  DEFB 04  34                  DEFB 34  CDA70E              CALL UNSTACK  C9                  RET  

In the NEW ROM, RST 28 means "perform floating point arithmetic". The data that follows tells it precisely what calculations it's supposed to do. The byte 04 means multiply - all of the shuffling around of the calculator stack is done automatically. The byte 34 is used after a RST 28 instruction to indicate that there is no more data to come, and the next machine code instruction should follow.

The RST 28 data codes are:

ADD    0F  SUB    03  MULT   04  DIV    05  

Don't forget you'll need a byte 34 as well though, to end the data.

You may be wondering what happens if the number on the top of the calculator stack is not an integer between 0 and 65535 (the maximum value any two byte register can hold). Well my first answer would be "try it for yourself and find out". Write a program that adds 8001 to 8001. Write a program that divides eight by three, then a program that divides seven by three. Write a program that subtracts five from zero, and another that subtracts a thousand from zero. But for those of you who are impatient I'll tell you anyway.

If the number at the top of the calculator stack is greater than 65535 then attempting to "unstack" it into BC will result in the program returning to command mode in fact - stopping with error code B (which means out of range).

If the number is a decimal then it will be rounded up or down (not just INTed) to the nearesr whole number. If the decimal part is less than 0.5 it will be rounded down, and if the decimal part is greater than or equal to 0.5 it will be rounded up.

If the number is negative then error B will result, causing an immediate return to BASIC and stopping the program, if there is one.

RST 28 allows you to do much, much more than just simple arithmetic. All of the functions of the ZX81 are available to you. The data code for any particular function is just the character code of that function minus AB. For instance, the character code of SIN is C7. C7 minus AB is 1C (if you don't believe me we'll do it in decimal - 199 minus 171 is 28). This means we can find the SIN of the number at the top of the calculator stack using the sequence:

EF                  RST 28  1C                  DEFB 1C (SIN)  34                  DEFB 34 (Exit)  

To multiply two numbers (at the top of the calculator stack) together and then find the square root of the result we can use the sequence:

EF                  RST 28  04                  DEFB 04 (MULT)  25                  DEFB 25 (SQR)  34                  DEFB 34 (Exit)  

If you're not absolutely convinced yet, run this program, which multiplies five by twenty, and then takes the square root.

3E05                LD A,05  CD1915              CALL STACKA  CD1D15              CALL STACKA  3E14                LD A,14  CD1915              CALL STACKA  CD1D15              CALL STACKA  EF                  RST 28  04                  DEFB 04 (MULT)  25                  DEFB 25 (SQR)  34                  DEFB 34 (Exit)  CDA70E              CALL UNSTACK  C9                  RET  

You'll notice that this is the first time we've used more than one code in the RST 28 data. In fact you can use as many as you like, provided you end the list with 34.

To save you working it out for yourselves here is a list of the available functions that we are ready to use, together with their appropriate RST 28 code:

FUNCTION  CODE      FUNCTION  CODE  CODE      19        EXP       23  VAL       1A        INT       24  LEN       1B        SQR       25  SIN       1C        SGN       26  COS       1D        ABS       27  TAN       1E        PEEK      28  ASN       1F        USR       29  ACS       20        STR$      2A  ATN       21        CHR$      2B  LN        22        NOT       2C  

Some of the entries in that list may surprise you. For instance we have the use of USR. This is very confusing - being allowed to use USR in the middle of a USR routine - but it's not really. Here's how it works. You've worked your way through a lot of RST 28 data, done a lot of calculation, and now you come across the code 29. What happens next is that the number at the top of the stack should be an integer between 0 and 65535 - or else you'll get an error B. This address is treated as a subroutine and CALLed. This subroutine will run exactly as you'd expect it to. When it's over (i.e. when a RET instruction is reached) the machine will go back to interpreting the RST 28 data from the next byte. USR will of course leave a new value at the top of the stack - the value held by BC at the end of the subroutine.

PEEK works in the same way, finding an address, PEEKing there, then pushing the result to the calculator stack.

All of the functions when used in this way will remove the number currently at the top of the calculator stack and replace it by a new one. For instance, if the number at the top of the stack is 3.5 and the function INT is called, the 3.5 will be removed and replaced by a new value, 3.

The string functions CODE, VAL, and LEN, also CHR$ and STR$ need a small amount of explaining. You see, as well as storing numbers, the calculator stack can also store strings, so if you start off with the number 2000 on the top of the stack, and you then call STR$ (by using code 2A in a RST 28 instruction) then the item at the top of the calculator stack will now be the string "2000". You can demonstrate this with the following:

01D007              LD BC,07D0  CD1C15              CALL STACKBC  CD2015              CALL STACKBC         EF                  RST 28  2A                  DEFB 2A (STR$)  19                  DEFB 19 (CODE)  34                  DEFB 34 (Exit)  CDA70E              CALL UNSTACK  C9                  RET  

This should produce the result of CODE STR$ 2000. Does it?

USING THE CALCULATOR'S MEMORY

If you take a quick glance at the manual you'll see that one of the system variables, MEMBOT, is thirty bytes long. This is the calculator's memory area. A quick calculation involving dividing by five, if you're up to it, shows that this leaves enough room to store six different five byte numbers. The six different areas of memory may each be used by RST 28 to store, or retrieve, numbers (but numbers only) from the top of the calculator stack. There are twelve different codes to achieve this - these are:

C0        stores number in memory location 0  C1        stores number in memory location 1  C2        stores number in memory location 2  C3        stores number in memory location 3  C4        stores number in memory location 4  C5        stores number in memory location 5    E0        recalls number from memory location 0  E1        recalls number from memory location 1  E2        recalls number from memory location 2  E3        recalls number from memory location 3  E4        recalls number from memory location 4  E5        recalls number from memory location 5  

Storing a number copies it from the top of the stack, and recalling a number simply places it at the end of the stack - it doesn't overwrite the previous top item.

Let's see how we can use this. Suppose we want to find SIN X+COS X. We must use the following technique. Assume that X is at the top of the stack.

EF                  RST 28  C0                  DEFB C0 (STORE0)  1C                  DEFB 1C (SIN)  E0                  DEFB E0 (RCALL0)  1D                  DEFB 1D (COS)  0F                  DEFB 0F (ADD)  34                  DEFB 34 (Exit)  

Note that the SIN routine changes X into SIN X. When we again recall X there are now two items on the stack: SIN X and X. The COS routine changes X into COS X, so that the two items on the stack, are now SIN X, and COS X. The ADD routine will replace both of these by one single number - the answer we want - SIN X plus COS X.

We have now performed a fairly complex trigonometric function in just eight bytes!

Let's see how we can remove a floating point number from the stack without restricting ourselves to integers less than 65536. The way the ROM does it is like this:

2A1C40              LD HL,(STKEND)  2B                  DEC HL  46                  LD B,(HL)  2B                  DEC HL  4E                  LD C,(HL)  2B                  DEC HL  56                  LD D,(HL)  2B                  DEC HL  5E                  LD E,(HL)  2B                  DEC HL  7E                  LD A,(HL)  221C40              LD (STKEND),HL  

As you can probably see for yourself, a five byte number is removed from the stack and stored in the registers A, E, D, C, and B (in that order). You can CALL this routine from address 13E4 13F8.

If the first item in the variable store is X then having popped SIN X plus COS X from the stack you can then store the result back in X as follows:

2A1040              LD HL,(VARS)  23                  INC HL  77                  LD (HL),A  23                  INC HL  73                  LD (HL),E  23                  INC HL  72                  LD (HL),D  23                  INC HL  71                  LD (HL),C  23                  INC HL  70                  LD (HL),B  C9                  RET  

You can see that it takes more bytes to store the answer than it does to find it in the first place!

Let's see what else we can do with RST 28. We can use the logical functions AND and OR (that is BASIC AND and BASIC OR). Both of these are available from RST 28, having byte codes 08 and 07 respectively. Also you can SWAP the two numbers at the top of the stack. Code 01 will do this.

The following sequence will raise one number to the power of another. Can you see why? After RST 28: 01 22 04 23 34.

[Thunor: Well that's it folks! After 61 days I've finally finished my HTML transcription of this book, on the same day that BBC4 showed the comedy docudrama Micro Men (->), dramatising the rivalry between Sir Clive Sinclair and Chris Curry of Acorn in the early 80s - fantastic stuff :)]

[ADDENDUM]

[Thunor: Just as I started to transcribe this chapter I received an email from tabbycat <tibbytab AT tesco DOT net> warning me about the forthcoming ROM address errors, therefore I was extra vigilant and I've (hopefully) flushed them all out; many thanks for the heads-up.]

 

 

A Listing of the Program HEXLD3

[Thunor: Note that the book simply includes a Sinclair printer printout showing an address, a byte and a character, and so I have decided to concatenate the listing from chapter 9.]

HEXLD3 - NEW ROM

Program Organisation  The HEXLD3 machine code is stored independently  within a line 1 REM statement of 255 bytes in size.    The user writes his/her machine code programs to  somewhere else and uses RUN 400 to copy it in and  subsequently out of an array to save it.    Decimal  Hex     Routine/Variable  16514    4082    HPRINT  16531    4093    BEGIN  16533    4095    ADDRESS  16535    4097    ADD2  16537    4099    LIMIT  16539    409B    HLIST  16589    40CD    WRITE  16635    40FB    ARRAY  16651    410B    STORE  16669    411D    RETRIEVE  16687    412F    INSERT  16732    415C    DELETE    Program Listing  4082: F5        HPRINT    PUSH AF  4083: E6F0                AND F0  4085: 1F                  RRA  4086: 1F                  RRA  4087: 1F                  RRA  4088: 1F                  RRA  4089: C61C                ADD A,1C  408B: D7                  RST 10  408C: F1                  POP AF  408D: E60F                AND 0F  408F: C61C                ADD A,1C  4091: D7                  RST 10  4092: C9                  RET  17 bytes.    4093: 0000      BEGIN     DEFB 00 00  4095: 0000      ADDRESS   DEFB 00 00  4097: 0000      ADD2      DEFB 00 00  4099: 0000      LIMIT     DEFB 00 00  8 bytes.    409B: 2A9940    HLIST     LD HL,(LIMIT)  409E: 229740              LD (ADD2),HL  40A1: 54                  LD D,H  40A2: 5D                  LD E,L  40A3: 2A9540              LD HL,(ADDRESS)  40A6: A7        NXTAD     AND A  40A7: ED52                SBC HL,DE  40A9: 19                  ADD HL,DE  40AA: 301F                JR NC,DONE  40AC: 7C                  LD A,H  40AD: CD8240              CALL HPRINT  40B0: 7D                  LD A,L  40B1: CD8240              CALL HPRINT  40B4: AF                  XOR A  40B5: D7                  RST 10  40B6: 7E                  LD A,(HL)  40B7: CD8240              CALL HPRINT  40BA: CB76                BIT 6,(HL)  40BC: 2004                JR NZ,NOPRINT  40BE: AF                  XOR A  40BF: D7                  RST 10  40C0: 7E                  LD A,(HL)  40C1: D7                  RST 10  40C2: 3E76      NOPRINT   LD A,76  40C4: D7                  RST 10  40C5: 23                  INC HL  40C6: 229540              LD (ADDRESS),HL  40C9: 18DB                JR NXTAD  40CB: CF        DONE      RST 08  40CC: 00                  DEFB 00  50 bytes.    40CD: 2A1040    WRITE     LD HL,(VARS)  40D0: 23                  INC HL  40D1: 46                  LD B, (HL)  40D2: 23                  INC HL  40D3: CB28                SRA B  40D5: 28F4                JR Z,DONE  40D7: ED5B9540            LD DE,(ADDRESS)  40DB: 23        NEXTBYTE  INC HL  40DC: 7E                  LD A,(HL)  40DD: 87                  ADD A,A  40DE: 87                  ADD A,A  40DF: 87                  ADD A,A  40E0: 87                  ADD A,A  40E1: 23                  INC HL  40E2: 86                  ADD A,(HL)  40E3: C624                ADD A,24  40E5: 12                  LD (DE),A  40E6: 13                  INC DE  40E7: ED539540            LD (ADDRESS),DE  40EB: E5                  PUSH HL  40EC: 2A9940              LD HL,(LIMIT)  40EF: ED52                SBC HL,DE  40F1: E1                  POP HL  40F2: 3004                JR NC,CHECK  40F4: ED539940            LD (LIMIT),DE  40F8: 10E1      CHECK     DJNZ NEXTBYTE  40FA: C9                  RET  46 bytes.    40FB: 2A9940    ARRAY     LD HL,(LIMIT)  40FE: ED5B9340            LD DE,(BEGIN)  4102: A7                  AND A  4103: ED52                SBC HL,DE  4105: 229740              LD (ADD2),HL  4108: 44                  LD B,H  4109: 4D                  LD C,L  410A: C9                  RET  16 bytes.    410B: 2A1040    STORE     LD HL,(VARS)  410E: 110600              LD DE,0006  4111: 19                  ADD HL,DE  4112: EB                  EX DE,HL  4113: 2A9340              LD HL,(BEGIN)  4116: ED4B9740            LD BC,(ADD2)  411A: EDB0                LDIR  411C: C9                  RET  18 bytes.    411D: 2A1040    RETRIEVE  LD HL,(VARS)  4120: 110600              LD DE,0006  4123: 19                  ADD HL,DE  4124: ED5B9340            LD DE,(BEGIN)  4128: ED4B9740            LD BC,(ADD2)  412C: EDB0                LDIR  412E: C9                  RET  18 bytes.    412F: 2A1040    INSERT    LD HL,(VARS)  4132: 23                  INC HL  4133: 4E                  LD C,(HL)  4134: 23                  INC HL  4135: 46                  LD B,(HL)  4136: CB28      COPYUP    SRA B  4138: CB19                RR C  413A: 2002                JR NZ,NOTEMPTY  413C: CF                  RST 08  413D: 08                  DEFB 08  413E: C5        NOTEMPTY  PUSH BC  413F: 2A9940              LD HL,(LIMIT)  4142: ED5B9540            LD DE,(ADDRESS)  4146: A7                  AND A  4147: ED52                SBC HL,DE  4149: 23                  INC HL  414A: 44                  LD B,H  414B: 4D                  LD C,L  414C: E1                  POP HL  414D: ED5B9940            LD DE,(LIMIT)  4151: 19                  ADD HL,DE  4152: 229940              LD (LIMIT),HL  4155: EB                  EX DE,HL  4156: EDB8                LDDR  4158: CDCD40              CALL WRITE  415B: C9                  RET  45 bytes.    415C: 2A9940    DELETE    LD HL,(LIMIT)  415F: ED5B9740            LD DE,(ADD2)  4163: D5                  PUSH DE  4164: A7                  AND A  4165: ED52                SBC HL,DE  4167: 44                  LD B,H  4168: 4D                  LD C,L  4169: E1                  POP HL  416A: 23                  INC HL  416B: ED5B9540            LD DE,(ADDRESS)  416F: EDB0                LDIR  4171: 1B                  DEC DE  4172: ED539940            LD (LIMIT),DE  4176: CF                  RST 08  4177: 08                  DEFB 08  28 bytes.    Total 246 bytes.    The BASIC Part    1 REM 255-arbitrary-characters-to-store-HEXLD3   10 PRINT "LIST"   20 GOSUB 600   30 RAND USR 16539    100 PRINT "WRITE"  110 GOSUB 600  120 INPUT A$  130 PRINT A$;"  ";  140 RAND USR 16589  150 GOTO 120    200 PRINT "INSERT"  210 GOSUB 600  220 INPUT A$  230 PRINT A$;"  ";  240 RAND USR 16687  250 GOTO 220    300 PRINT "DELETE"  305 PRINT "START ";    [Thunor: I added this; it's optional]  310 GOSUB 600  320 LET A=16535  325 PRINT "END ";      [Thunor: I added this; it's optional]  330 GOSUB 610  340 RAND USR 16732    400 DIM O$(USR 16635)  410 RAND USR 16651  420 SAVE "HEXLD3"  500 RAND USR 16669  510 CLEAR  520 STOP    600 LET A=16533  610 PRINT "ADDRESS ";  620 INPUT A$  630 PRINT A$  640 POKE A+1,16*CODE A$+CODE A$(2)-476  650 POKE A,16*CODE A$(3)+CODE A$(4)-476  660 CLEAR  670 RETURN    Operating Instructions  Don't forget that if you intend to write in high memory then  you need to set RAMTOP and execute NEW before loading HEXLD3.    To initialise HEXLD3 for a new program:  1. Type RUN 100 and enter the address of the variable BEGIN (4093h).  2. Input the start address of the program you are about to write in     reverse order i.e. 4C00 would be "004C".  3. Type newline on its own to quit writing.  4. Type RUN 100 to begin inputting a program.  5. Type RUN 400 to save your machine code program.    Thereafter:  RUN        To List your machine code program.             Use CONT to continue listing up until LIMIT.  RUN 100    To Write new machine code.             Type newline on its own to quit.  RUN 200    To Insert new machine code.             Type newline on its own to quit.  RUN 300    To Delete previous machine code.             Enter the start and end addresses to delete.  RUN 400    To Save your machine code program.             Your machine code is automatically restored on load.  

[Download available for 16K ZX81 -> chapter09-hexld3.p]

[HEXLD3 - OLD ROM]

[Thunor: Adapted from the NEW ROM version by myself as the author didn't supply a complete OLD ROM version. This version is Draughts ready, meaning that the machine code relocation that NEW ROM users will be asked to do in chapter 11 is unnecessary (the machine code is already located where Draughts requires it to be), the modification to BASIC line 500 to execute RETRIEVE from inside the O array has already been applied (BASIC line 504), and the issue with RETRIEVE not referencing BEGIN and ADD2 from within the O array has been fixed in BASIC lines 500 to 503.

Program Organisation  The HEXLD3 machine code is initially written to  4A1A and then stored within an array on save. When  HEXLD3 is reloaded it is copied from the array  back to its original memory location by executing  GO TO 500. Note that the value stored in BEGIN is  4A00 and so there are 26 unused bytes before 4A1A.    The user's own code appends the HEXLD3 machine code.  So when the user executes RUN 400 to copy his work  to an array for saving, he also saves HEXLD3.  Subsequently restoring the code after reloading the  program with GO TO 500 will restore HEXLD3 to 4A1A.    Decimal  Hex     Routine/Variable  18970    4A1A    APRINT  18983    4A27    HPRINT  19004    4A3C    BEGIN  19006    4A3E    ADDRESS  19008    4A40    ADD2  19010    4A42    LIMIT  19012    4A44    HLIST  19072    4A80    WRITE  19125    4AB5    ARRAY  19143    4AC7    STORE  19161    4AD9    RETRIEVE  19179    4AEB    INSERT  19231    4B1F    DELETE    Program Listing  4A1A: E5        APRINT    PUSH HL  4A1B: D9                  EXX  4A1C: F5                  PUSH AF  4A1D: CDE006              CALL PRPOS  4A20: F1                  POP AF  4A21: CD2007              CALL PRINT  4A24: D9                  EXX  4A25: E1                  POP HL  4A26: C9                  RET  13 bytes.    4A27: F5        HPRINT    PUSH AF  4A28: E6F0                AND F0  4A2A: 1F                  RRA  4A2B: 1F                  RRA  4A2C: 1F                  RRA  4A2D: 1F                  RRA  4A2E: C61C                ADD A,1C  4A30: CD1A4A              CALL APRINT  4A33: F1                  POP AF  4A34: E60F                AND 0F  4A36: C61C                ADD A,1C  4A38: CD1A4A              CALL APRINT  4A3B: C9                  RET  21 bytes.    4A3C: 0000      BEGIN     DEFB 00 00  4A3E: 0000      ADDRESS   DEFB 00 00  4A40: 0000      ADD2      DEFB 00 00  4A42: 0000      LIMIT     DEFB 00 00  8 bytes.    4A44: 2A424A    HLIST     LD HL,(LIMIT)  4A47: 22404A              LD (ADD2),HL  4A4A: 54                  LD D,H  4A4B: 5D                  LD E,L  4A4C: 2A3E4A              LD HL,(ADDRESS)  4A4F: 0616                LD B,16  4A51: A7        NXTAD     AND A  4A52: ED52                SBC HL,DE  4A54: 19                  ADD HL,DE  4A55: 3027                JR NC,DONE  4A57: 7C                  LD A,H  4A58: CD274A              CALL HPRINT  4A5B: 7D                  LD A,L  4A5C: CD274A              CALL HPRINT  4A5F: AF                  XOR A  4A60: CD1A4A              CALL APRINT  4A63: 7E                  LD A,(HL)  4A64: CD274A              CALL HPRINT  4A67: CB76                BIT 6,(HL)  4A69: 2008                JR NZ,NOPRINT  4A6B: AF                  XOR A  4A6C: CD1A4A              CALL APRINT  4A6F: 7E                  LD A,(HL)  4A70: CD1A4A              CALL APRINT  4A73: 3E76      NOPRINT   LD A,76  4A75: CD1A4A              CALL APRINT  4A78: 23                  INC HL  4A79: 223E4A              LD (ADDRESS),HL  4A7C: 10D3                DJNZ NXTAD  4A7E: CF        DONE      RST 08  4A7F: 00                  DEFB 00  60 bytes.    4A80: 2A0840    WRITE     LD HL,(VARS)  4A83: E5                  PUSH HL  4A84: 06FF                LD B,FF  4A86: 23        ANOTHER   INC HL  4A87: 7E                  LD A,(HL)  4A88: 04                  INC B  4A89: 3D                  DEC A  4A8A: 20FA                JR NZ,ANOTHER  4A8C: E1                  POP HL  4A8D: CB28                SRA B  4A8F: 28ED                JR Z,DONE  4A91: ED5B3E4A            LD DE,(ADDRESS)  4A95: 23        NEXTBYTE  INC HL  4A96: 7E                  LD A,(HL)  4A97: 87                  ADD A,A  4A98: 87                  ADD A,A  4A99: 87                  ADD A,A  4A9A: 87                  ADD A,A  4A9B: 23                  INC HL  4A9C: 86                  ADD A,(HL)  4A9D: C624                ADD A,24  4A9F: 12                  LD (DE),A  4AA0: 13                  INC DE  4AA1: ED533E4A            LD (ADDRESS),DE  4AA5: E5                  PUSH HL  4AA6: 2A424A              LD HL,(LIMIT)  4AA9: ED52                SBC HL,DE  4AAB: E1                  POP HL  4AAC: 3004                JR NC,CHECK  4AAE: ED53424A            LD (LIMIT),DE  4AB2: 10E1      CHECK     DJNZ NEXTBYTE  4AB4: C9                  RET  53 bytes.    4AB5: 2A424A    ARRAY     LD HL,(LIMIT)  4AB8: ED5B3C4A            LD DE,(BEGIN)  4ABC: A7                  AND A  4ABD: ED52                SBC HL,DE  4ABF: 22404A              LD (ADD2),HL  4AC2: CB2C                SRA H  4AC4: CB1D                RR L  4AC6: C9                  RET  18 bytes.    4AC7: 2A0840    STORE     LD HL,(VARS)  4ACA: 110600              LD DE,0006  4ACD: 19                  ADD HL,DE  4ACE: EB                  EX DE,HL  4ACF: 2A3C4A              LD HL,(BEGIN)  4AD2: ED4B404A            LD BC,(ADD2)  4AD6: EDB0                LDIR  4AD8: C9                  RET  18 bytes.    4AD9: 2A0840    RETRIEVE  LD HL,(VARS)  4ADC: 110600              LD DE,0006  4ADF: 19                  ADD HL,DE  4AE0: ED5B3C4A            LD DE,(BEGIN)  4AE4: ED4B404A            LD BC,(ADD2)  4AE8: EDB0                LDIR  4AEA: C9                  RET  18 bytes.    4AEB: 2A0840    INSERT    LD HL,(VARS)  4AEE: E5                  PUSH HL  4AEF: 01FFFF              LD BC,FFFF  4AF2: 23        MORE      INC HL  4AF3: 7E                  LD A,(HL)  4AF4: 03                  INC BC  4AF5: 3D                  DEC A  4AF6: 20FA                JR NZ,MORE  4AF8: E1                  POP HL  4AF9: CB28      COPYUP    SRA B  4AFB: CB19                RR C  4AFD: 2002                JR NZ,NOTEMPTY  4AFF: CF                  RST 08  4B00: 08                  DEFB 08  4B01: C5        NOTEMPTY  PUSH BC  4B02: 2A424A              LD HL,(LIMIT)  4B05: ED5B3E4A            LD DE,(ADDRESS)  4B09: A7                  AND A  4B0A: ED52                SBC HL,DE  4B0C: 23                  INC HL  4B0D: 44                  LD B,H  4B0E: 4D                  LD C,L  4B0F: E1                  POP HL  4B10: ED5B424A            LD DE,(LIMIT)  4B14: 19                  ADD HL,DE  4B15: 22424A              LD (LIMIT),HL  4B18: EB                  EX DE,HL  4B19: EDB8                LDDR  4B1B: CD804A              CALL WRITE  4B1E: C9                  RET  52 bytes.    4B1F: 2A424A    DELETE    LD HL,(LIMIT)  4B22: ED5B404A            LD DE,(ADD2)  4B26: D5                  PUSH DE  4B27: A7                  AND A  4B28: ED52                SBC HL,DE  4B2A: 44                  LD B,H  4B2B: 4D                  LD C,L  4B2C: E1                  POP HL  4B2D: 23                  INC HL  4B2E: ED5B3E4A            LD DE,(ADDRESS)  4B32: EDB0                LDIR  4B34: 1B                  DEC DE  4B35: ED53424A            LD (LIMIT),DE  4B39: CF                  RST 08  4B3A: 08                  DEFB 08  28 bytes.    Total 289 bytes.    The BASIC Part   10 PRINT "LIST"   20 GO SUB 600   30 RANDOMISE USR(19012)    100 PRINT "WRITE"  110 GO SUB 600  120 INPUT A$  130 PRINT A$;"  ";  140 RANDOMISE USR(19072)  150 GO TO 120    200 PRINT "INSERT"  210 GO SUB 600  220 INPUT A$  230 PRINT A$;"  ";  240 RANDOMISE USR(19179)  250 GO TO 220    300 PRINT "DELETE"  305 PRINT "START ";  310 GO SUB 600  320 LET A=19008  325 PRINT "END ";  330 GO SUB 610  340 RANDOMISE USR(19231)    400 DIM O(USR(19125))  410 RANDOMISE USR(19143)  420 SAVE    500 POKE 19004,PEEK(PEEK(16392)+PEEK(16393)*256+6+26+34)  501 POKE 19005,PEEK(PEEK(16392)+PEEK(16393)*256+6+26+35)  502 POKE 19008,PEEK(PEEK(16392)+PEEK(16393)*256+6+26+38)  503 POKE 19009,PEEK(PEEK(16392)+PEEK(16393)*256+6+26+39)  504 RANDOMISE USR(PEEK(16392)+PEEK(16393)*256+6+26+191)  520 STOP    600 LET A=19006  610 PRINT "ADDRESS ";  620 INPUT A$  630 PRINT A$  640 POKE A+1,16*CODE(A$)+CODE(TL$(A$))-476  650 POKE A,16*CODE(TL$(TL$(A$)))+CODE(TL$(TL$(TL$(A$))))-476  660 CLEAR  670 RETURN    Operating Instructions  1. Type GO TO 500 to restore HEXLD3's machine code     and your machine code to its correct memory location.  2. Type RUN 100 to begin inputting a program.  3. Type newline on its own to quit writing.  4. Type RUN 400 to save your machine code program.    Thereafter:  RUN        To List your machine code program.             Use CONT to continue listing up until LIMIT.  RUN 100    To Write new machine code.             Type newline on its own to quit.  RUN 200    To Insert new machine code.             Type newline on its own to quit.  RUN 300    To Delete previous machine code.             Enter the start and end addresses to delete.  RUN 400    To Save your machine code program.  GO TO 500  To Restore the machine code after loading.  

Additional content end.]

[Download available for 16K ZX80 -> chapter09-hexld3.o]

 

 

The System Variables

OLD ROM SYSTEM VARIABLES    NEW ROM SYSTEM VARIABLES  Decimal Hex   Name          Decimal Hex   Name  16384   4000  ERR_NR        16384   4000  ERR_NR  16385   4001  FLAGS         16385   4001  FLAGS  16386   4002  PPC           16386   4002  ERR_SP  16388   4004  E_ADDR        16388   4004  RAMTOP  16390   4006  E_PPC         16390   4006  MODE  16392   4008  VARS          16391   4007  PPC  16394   400A  E_LINE        16393   4009  VERSN  16396   400C  D_FILE        16394   400A  E_PPC  16398   400E  DF_EA         16396   400C  D_FILE  16400   4010  DF_END        16398   400E  DF_CC  16402   4012  DF_SZ         16400   4010  VARS  16403   4013  S_TOP         16402   4012  DEST  16405   4015  X_PTR         16404   4014  E_LINE  16407   4017  OLDPPC        16406   4016  CH_ADD  16409   4019  FLAGX         16408   4018  X_PTR  16410   401A  T_ADDR        16410   401A  STKBOT  16412   401C  SEED          16412   401C  STKEND  16414   401E  FRAMES        16414   401E  BERG  16416   4020  V_ADDR        16415   401F  MEM  16418   4022  ACC           16417   4021  SPARE1  16420   4024  S_POSN        16418   4022  DF_SZ  16422   4026  CH_ADD        16419   4023  S_TOP                              16421   4025  LAST_K                              16423   4027  DB_ST                              16424   4028  MARGIN                              16425   4029  NXTLIN                              16427   402B  OLDPPC                              16429   402D  FLAGX                              16430   402E  STRLEN                              16432   4030  T_ADDR                              16434   4032  SEED                              16436   4034  FRAMES                              16438   4036  COORDS                              16440   4038  PR_CC                              16441   4039  S_POSN                              16443   403B  CDFLAG                              16444   403C  PRBUFF                              16477   405D  MEMBOT                              16507   407B  SPARE2  
+-------+-----+-----+----+--------------------------------------------------+  |SYSTEM |OLD  |NEW  |NO. |                                                  |  |VARIA- |ROM  |ROM  |OF  |                                                  |  |BLES   |ADDR.|ADDR.|BYT.|PURPOSE                                           |  +-------+-----+-----+----+--------------------------------------------------+  |ACC    |4022 |-    |2   |Value of last expression                          |  |BERG   |-    |401E |1   |Used by floating point calculator                 |  |CDFLAG |-    |403B |1   |Flags relating to FAST/SLOW mode                  |  |CH_ADD |4026 |4016 |2   |Address of the next character to interpret        |  |COORDS |-    |4036 |2   |Coordinates of last point PLOTed                  |  |D_FILE |400C |400C |2   |Address of start of display file                  |  |DB_ST  |-    |4027 |1   |Debounce status of keyboard                       |  |DEST   |-    |4012 |2   |Address of variable being assigned                |  |DF_CC  |-    |400E |2   |Address of print position within display file     |  |DF_EA  |400E |-    |2   |Address of start of lower part of screen          |  |DF_END |4010 |-    |2   |Address of end of display file                    |  |DF_SZ  |4012 |4022 |2   |Number of lines in lower part of screen           |  |E_ADDR |4004 |-    |2   |Address of cursor in edit line                    |  |E_LINE |400A |4014 |2   |Address of start of edit line                     |  |E_PPC  |4006 |400A |2   |Line number of line with cursor                   |  |ERR_NR |4000 |4000 |1   |Current report code minus one                     |  |ERR_SP |-    |4002 |2   |Address of top of GOSUB stack                     |  |FLAGS  |4001 |4001 |1   |Various flags                                     |  |FLAGX  |4019 |402D |1   |Various flags                                     |  |FRAMES |401E |4034 |2   |Updated once for every TV frame displayed         |  |LAST_K |-    |4025 |2   |Keyboard scan taken after the last TV frame       |  |MARGIN |-    |4028 |1   |Number of blank lines above or below picture      |  |MEM    |-    |401F |2   |Address of start of calculator's memory area      |  |MEMBOT |-    |405D |1E  |Area which may be used for calculator memory      |  |MODE   |-    |4006 |1   |Current cursor mode                               |  |NXTLIN |-    |4029 |2   |Address of next program line to be executed       |  |OLDPPC |4017 |402B |2   |Line number to which CONT/CONTINUE jumps          |  |PPC    |4002 |4007 |2   |Line number of line being executed                |  |PR_CC  |-    |4038 |1   |Address of LPRINT position (high part assumed 40) |  |PRBUFF |-    |403C |21h |Buffer to store LPRINT output                     |  |RAMTOP |-    |4004 |2   |Address of reserved area (not wiped out by NEW)   |  |S_POSN |4024 |4039 |2   |Coordinates of print position                     |  |S_TOP  |4013 |4023 |2   |Line number of line at top of screen              |  |SEED   |401C |4032 |2   |Seed for random number generator                  |  |SPARE1 |-    |4021 |1   |One spare byte                                    |  |SPARE2 |-    |407B |2   |Two spare bytes                                   |  |STKBOT |-    |401A |2   |Address of calculator stack                       |  |STKEND |-    |401C |2   |Address of end of calculator stack                |  |STRLEN |-    |402E |2   |Information concerning assigning of strings       |  |T_ADDR |401A |4030 |2   |Address of next item in syntax table              |  |V_ADDR |4020 |-    |2   |Address of variable name to be assigned           |  |VARS   |4008 |4010 |2   |Address of start of variables area                |  |VERSN  |-    |4009 |1   |First system variable to be SAVEd                 |  |X_PTR  |4015 |4018 |2   |Address of char. preceding syntax error marker    |  +-------+-----+-----+----+--------------------------------------------------+  

Memory Organisation

[Thunor: Note that the book simply includes a list of addresses, and so I have decided to implement a diagram that presents the addresses in a more informative way.]

                 OLD ROM                NEW ROM    Top of memory  --+------------------+   +------------------+-- Top of memory                   |                  |   | Reserved area    |                   |                  |   +------------------+-- (RAMTOP)                   |                  |   | GOSUB stack      |                   |                  |   +------------------+-- (ERR_SP)                   |                  |   | Machine stack    |                   |                  |   +------------------+-- SP                   | Machine stack    |   | Spare memory     |  SP             --+------------------+   +------------------+-- (STKEND)                   | Spare memory     | ^ | Calculator stack |  (DF_END)       --+------------------+ | +------------------+-- (STKBOT)                   | Screen           | | | Edit line        |  (D_FILE)       --+------------------+   +------------------+-- (E_LINE)                   | Edit line        |   | User variables   |  (E_LINE)       --+------------------+   +------------------+-- (VARS)                   | User variables   |   | Screen           |  (VARS)         --+------------------+   +------------------+-- (D_FILE)                   | User program     |   | User program     |  4028h (16424d) --+------------------+   +------------------+-- 407Dh (16509d)                   | System variables |   | System variables |  4000h (16384d) --+------------------+   +------------------+-- 4000h (16384d)  

 

 

 

A Conversion Table from Hex to Assembly

[Thunor: Key to symbols used:

  • d - A displacement e.g. 7B.
  • e - A single byte e.g. F7.
  • mn - A numerical constant e.g. 9E3D.
  • n - A numerical constant e.g. 3D.
  • pq - An absolute address e.g. 4C00.

Key end.]

ORDINARY

yx      0             1             2             3  --+-----------------------------------------------------  0 | NOP           LD BC,mn      LD (BC),A     INC BC  1 | DJNZ e        LD DE,mn      LD (DE),A     INC DE  2 | JR NZ,e       LD HL,mn      LD (pq),HL    INC HL  3 | JR NC,e       LD SP,mn      LD (pq),A     INC SP  4 | LD B,B        LD B,C        LD B,D        LD B,E  5 | LD D,B        LD D,C        LD D,D        LD D,E  6 | LD H,B        LD H,C        LD H,D        LD H,E  7 | LD (HL),B     LD (HL),C     LD (HL),D     LD (HL),E  8 | ADD A,B       ADD A,C       ADD A,D       ADD A,E  9 | SUB B         SUB C         SUB D         SUB E  A | AND B         AND C         AND D         AND E  B | OR B          OR C          OR D          OR E  C | RET NZ        POP BC        JP NZ,pq      JP pq  D | RET NC        POP DE        JP NC,pq      OUT (n),A  E | RET PO        POP HL        JP PO,pq      EX (SP),HL  F | RET P         POP AF        JP P,pq       DI        4             5             6             7  --+-----------------------------------------------------  0 | INC B         DEC B         LD B,n        RLCA  1 | INC D         DEC D         LD D,n        RLA  2 | INC H         DEC H         LD H,n        DAA  3 | INC (HL)      DEC (HL)      LD (HL),n     SCF  4 | LD B,H        LD B,L        LD B,(HL)     LD B,A  5 | LD D,H        LD D,L        LD D,(HL)     LD D,A  6 | LD H,H        LD H,L        LD H,(HL)     LD H,A  7 | LD (HL),H     LD (HL),L     HALT          LD (HL),A  8 | ADD A,H       ADD A,L       ADD A,(HL)    ADD A,A  9 | SUB H         SUB L         SUB (HL)      SUB A  A | AND H         AND L         AND (HL)      AND A  B | OR H          OR L          OR (HL)       OR A  C | CALL NZ,pq    PUSH BC       ADD A,n       RST 00  D | CALL NC,pq    PUSH DE       SUB n         RST 10  E | CALL PO,pq    PUSH HL       AND n         RST 20  F | CALL P,pq     PUSH AF       OR n          RST 30        8             9             A             B  --+-----------------------------------------------------  0 | EX AF,AF'     ADD HL,BC     LD A,(BC)     DEC BC  1 | JR e          ADD HL,DE     LD A,(DE)     DEC DE  2 | JR Z,e        ADD HL,HL     LD HL,(pq)    DEC HL  3 | JR C,e        ADD HL,SP     LD A,(pq)     DEC SP  4 | LD C,B        LD C,C        LD C,D        LD C,E  5 | LD E,B        LD E,C        LD E,D        LD E,E  6 | LD L,B        LD L,C        LD L,D        LD L,E  7 | LD A,B        LD A,C        LD A,D        LD A,E  8 | ADC A,B       ADC A,C       ADC A,D       ADC A,E  9 | SBC A,B       SBC A,C       SBC A,D       SBC A,E  A | XOR B         XOR C         XOR D         XOR E  B | CP B          CP C          CP D          CP E  C | RET Z         RET           JP Z,pq       #  D | RET C         EXX           JP C,pq       IN A,(n)  E | RET PE        JP (HL)       JP PE,pq      EX DE,HL  F | RET M         LD SP,HL      JP M,pq       EI        C             D             E             F  --+-----------------------------------------------------  0 | INC C         DEC C         LD C,n        RRCA  1 | INC E         DEC E         LD E,n        RRA  2 | INC L         DEC L         LD L,n        CPL  3 | INC A         DEC A         LD A,n        CCF  4 | LD C,H        LD C,L        LD C,(HL)     LD C,A  5 | LD E,H        LD E,L        LD E,(HL)     LD E,A  6 | LD L,H        LD L,L        LD L,(HL)     LD L,A  7 | LD A,H        LD A,L        LD A,(HL)     LD A,A  8 | ADC A,H       ADC A,L       ADC A,(HL)    ADC A,A  9 | SBC A,H       SBC A,L       SBC A,(HL)    SBC A,A  A | XOR H         XOR L         XOR (HL)      XOR A  B | CP H          CP L          CP (HL)       CP A  C | CALL Z,pq     CALL pq       ADC A,n       RST 08  D | CALL C,pq     #             SBC A,n       RST 18  E | CALL PE,pq    #             XOR n         RST 28  F | CALL M,pq     #             CP n          RST 38  

AFTER CB

CByx      0        1        2        3        4        5        6           7  --+--------------------------------------------------------------------------  0 | RLC B    RLC C    RLC D    RLC E    RLC H    RLC L    RLC (HL)    RLC A  1 | RL B     RL C     RL D     RL E     RL H     RL L     RL (HL)     RL A  2 | SLA B    SLA C    SLA D    SLA E    SLA H    SLA L    SLA (HL)    SLA A  3 | -        -        -        -        -        -        -           -  4 | BIT 0,B  BIT 0,C  BIT 0,D  BIT 0,E  BIT 0,H  BIT 0,L  BIT 0,(HL)  BIT 0,A  5 | BIT 2,B  BIT 2,C  BIT 2,D  BIT 2,E  BIT 2,H  BIT 2,L  BIT 2,(HL)  BIT 2,A  6 | BIT 4,B  BIT 4,C  BIT 4,D  BIT 4,E  BIT 4,H  BIT 4,L  BIT 4,(HL)  BIT 4,A  7 | BIT 6,B  BIT 6,C  BIT 6,D  BIT 6,E  BIT 6,H  BIT 6,L  BIT 6,(HL)  BIT 6,A  8 | RES 0,B  RES 0,C  RES 0,D  RES 0,E  RES 0,H  RES 0,L  RES 0,(HL)  RES 0,A  9 | RES 2,B  RES 2,C  RES 2,D  RES 2,E  RES 2,H  RES 2,L  RES 2,(HL)  RES 2,A  A | RES 4,B  RES 4,C  RES 4,D  RES 4,E  RES 4,H  RES 4,L  RES 4,(HL)  RES 4,A  B | RES 6,B  RES 6,C  RES 6,D  RES 6,E  RES 6,H  RES 6,L  RES 6,(HL)  RES 6,A  C | SET 0,B  SET 0,C  SET 0,D  SET 0,E  SET 0,H  SET 0,L  SET 0,(HL)  SET 0,A  D | SET 2,B  SET 2,C  SET 2,D  SET 2,E  SET 2,H  SET 2,L  SET 2,(HL)  SET 2,A  E | SET 4,B  SET 4,C  SET 4,D  SET 4,E  SET 4,H  SET 4,L  SET 4,(HL)  SET 4,A  F | SET 6,B  SET 6,C  SET 6,D  SET 6,E  SET 6,H  SET 6,L  SET 6,(HL)  SET 6,A        8        9        A        B        C        D        E           F  --+--------------------------------------------------------------------------  0 | RRC B    RRC C    RRC D    RRC E    RRC H    RRC L    RRC (HL)    RRC A  1 | RR B     RR C     RR D     RR E     RR H     RR L     RR (HL)     RR A  2 | SRA B    SRA C    SRA D    SRA E    SRA H    SRA L    SRA (HL)    SRA A  3 | SRL B    SRL C    SRL D    SRL E    SRL H    SRL L    SRL (HL)    SRL A  4 | BIT 1,B  BIT 1,C  BIT 1,D  BIT 1,E  BIT 1,H  BIT 1,L  BIT 1,(HL)  BIT 1,A  5 | BIT 3,B  BIT 3,C  BIT 3,D  BIT 3,E  BIT 3,H  BIT 3,L  BIT 3,(HL)  BIT 3,A  6 | BIT 5,B  BIT 5,C  BIT 5,D  BIT 5,E  BIT 5,H  BIT 5,L  BIT 5,(HL)  BIT 5,A  7 | BIT 7,B  BIT 7,C  BIT 7,D  BIT 7,E  BIT 7,H  BIT 7,L  BIT 7,(HL)  BIT 7,A  8 | RES 1,B  RES 1,C  RES 1,D  RES 1,E  RES 1,H  RES 1,L  RES 1,(HL)  RES 1,A  9 | RES 3,B  RES 3,C  RES 3,D  RES 3,E  RES 3,H  RES 3,L  RES 3,(HL)  RES 3,A  A | RES 5,B  RES 5,C  RES 5,D  RES 5,E  RES 5,H  RES 5,L  RES 5,(HL)  RES 5,A  B | RES 7,B  RES 7,C  RES 7,D  RES 7,E  RES 7,H  RES 7,L  RES 7,(HL)  RES 7,A  C | SET 1,B  SET 1,C  SET 1,D  SET 1,E  SET 1,H  SET 1,L  SET 1,(HL)  SET 1,A  D | SET 3,B  SET 3,C  SET 3,D  SET 3,E  SET 3,H  SET 3,L  SET 3,(HL)  SET 3,A  E | SET 5,B  SET 5,C  SET 5,D  SET 5,E  SET 5,H  SET 5,L  SET 5,(HL)  SET 5,A  F | SET 7,B  SET 7,C  SET 7,D  SET 7,E  SET 7,H  SET 7,L  SET 7,(HL)  SET 7,A  

AFTER DD

DDyx      0            1            2            3  --+---------------------------------------------------  0 | -            -            -            -  1 | -            -            -            -  2 | -            LD IX,mn     LD (pq),IX   INC IX  3 | -            -            -            -  4 | -            -            -            -  5 | -            -            -            -  6 | -            -            -            -  7 | LD (IX+d),B  LD (IX+d),C  LD (IX+d),D  LD (IX+d),E  8 | -            -            -            -  9 | -            -            -            -  A | -            -            -            -  B | -            -            -            -  C | -            -            -            -  D | -            -            -            -  E | -            POP IX       -            EX (SP),IX  F | -            -            -            -        4            5            6            7  --+---------------------------------------------------  0 | -            -            -            -  1 | -            -            -            -  2 | -            -            -            -  3 | INC (IX+d)   DEC (IX+d)   LD (IX+d),n  -  4 | -            -            LD B,(IX+d)  -  5 | -            -            LD D,(IX+d)  -  6 | -            -            LD H,(IX+d)  -  7 | LD (IX+d),H  LD (IX+d),L  -            LD (IX+d),A  8 | -            -            ADD A,(IX+d) -  9 | -            -            SUB (IX+d)   -  A | -            -            AND (IX+d)   -  B | -            -            OR (IX+d)    -  C | -            -            -            -  D | -            -            -            -  E | -            PUSH IX      -            -  F | -            -            -            -        8  9          A           B         C  D  E             F  --+----------------------------------------------------------  0 | -  ADD IX,BC  -           -         -  -  -             -  1 | -  ADD IX,DE  -           -         -  -  -             -  2 | -  ADD IX,IX  LD IX,(pq)  DEC IX    -  -  -             -  3 | -  ADD IX,SP  -           -         -  -  -             -  4 | -  -          -           -         -  -  LD C,(IX+d)   -  5 | -  -          -           -         -  -  LD E,(IX+d)   -  6 | -  -          -           -         -  -  LD L,(IX+d)   -  7 | -  -          -           -         -  -  LD A,(IX+d)   -  8 | -  -          -           -         -  -  ADC A,(IX+d)  -  9 | -  -          -           -         -  -  SBC (IX+d)    -  A | -  -          -           -         -  -  XOR (IX+d)    -  B | -  -          -           -         -  -  CP (IX+d)     -  C | -  -          -           #         -  -  -             -  D | -  -          -           -         -  -  -             -  E | -  JP (IX)    -           EX DE,IX  -  -  -             -  F | -  LD SP,IX   -           -         -  -  -             -  

AFTER ED

EDyx      0         1          2          3           4    5     6     7  --+--------------------------------------------------------------------  0 | -         -          -          -           -    -     -     -  1 | -         -          -          -           -    -     -     -  2 | -         -          -          -           -    -     -     -  3 | -         -          -          -           -    -     -     -  4 | IN B,(C)  OUT (C),B  SBC HL,BC  LD (pq),BC  NEG  RETN  IM 0  LD I,A  5 | IN D,(C)  OUT (C),D  SBC HL,DE  LD (pq),DE  -    -     IM 1  LD A,I  6 | IN H,(C)  OUT (C),H  SBC HL,HL  -           -    -     -     RRD  7 | -         -          SBC HL,SP  LD (pq),SP  -    -     -     -  8 | -         -          -          -           -    -     -     -  9 | -         -          -          -           -    -     -     -  A | LDI       CPI        INI        OUTI        -    -     -     -  B | LDIR      CPIR       INIR       OTIR        -    -     -     -  C | -         -          -          -           -    -     -     -  D | -         -          -          -           -    -     -     -  E | -         -          -          -           -    -     -     -  F | -         -          -          -           -    -     -     -        8         9          A          B           C    D     E     F  --+--------------------------------------------------------------------  0 | -         -          -          -           -    -     -     -  1 | -         -          -          -           -    -     -     -  2 | -         -          -          -           -    -     -     -  3 | -         -          -          -           -    -     -     -  4 | IN C,(C)  OUT (C),C  ADC HL,BC  LD BC,(pq)  -    RETI  -     LD R,A  5 | IN E,(C)  OUT (C),E  ADC HL,DE  LD DE,(pq)  -    -     IM 2  LD A,R  6 | IN L,(C)  OUT (C),L  ADC HL,HL  -           -    -     -     RLD  7 | IN A,(C)  OUT (C),A  ADC HL,SP  LD SP,(pq)  -    -     -     -  8 | -         -          -          -           -    -     -     -  9 | -         -          -          -           -    -     -     -  A | LDD       CPD        IND        OUTD        -    -     -     -  B | LDDR      CPDR       INDR       OTDR        -    -     -     -  C | -         -          -          -           -    -     -     -  D | -         -          -          -           -    -     -     -  E | -         -          -          -           -    -     -     -  F | -         -          -          -           -    -     -     -  

AFTER FD

FDyx      0            1             2            3            4  --+-----------------------------------------------------------------  0 | -            -             -            -            -  1 | -            -             -            -            -  2 | -            LD IY,mn      LD (pq),IY   INC IY       -  3 | -            -             -            -            INC (IY+d)  4 | -            -             -            -            -  5 | -            -             -            -            -  6 | -            -             -            -            -  7 | LD (IY+d),B  LD (IY+d),C   LD (IY+d),D  LD (IY+d),E  LD (IY+d),H  8 | -            -             -            -            -  9 | -            -             -            -            -  A | -            -             -            -            -  B | -            -             -            -            -  C | -            -             -            -            -  D | -            -             -            -            -  E | -            POP IY        -            EX (SP),IY   -  F | -            -             -            -            -        5            6             7            8            9  --+-----------------------------------------------------------------  0 | -            -             -            -            ADD IY,BC  1 | -            -             -            -            ADD IY,DE  2 | -            -             -            -            ADD IY,IY  3 | DEC (IY+d)   LD (IY+d),n   -            -            ADD IY,SP  4 | -            LD B,(IY+d)   -            -            -  5 | -            LD D,(IY+d)   -            -            -  6 | -            LD H,(IY+d)   -            -            -  7 | LD (IY+d),L  -             LD (IY+d),A  -            -  8 | -            ADD A,(IY+d)  -            -            -  9 | -            SUB (IY+d)    -            -            -  A | -            AND (IY+d)    -            -            -  B | -            OR (IY+d)     -            -            -  C | -            -             -            -            -  D | -            -             -            -            -  E | PUSH IY      -             -            -            JP (IY)  F | -            -             -            -            LD SP,IY        A           B            C           D           E             F  --+-----------------------------------------------------------------  0 | -           -            -           -           -             -  1 | -           -            -           -           -             -  2 | LD IY,(pq)  DEC IY       -           -           -             -  3 | -           -            -           -           -             -  4 | -           -            -           -           LD C,(IY+d)   -  5 | -           -            -           -           LD E,(IY+d)   -  6 | -           -            -           -           LD L,(IY+d)   -  7 | -           -            -           -           LD A,(IY+d)   -  8 | -           -            -           -           ADC A,(IY+d)  -  9 | -           -            -           -           SBC (IY+d)    -  A | -           -            -           -           XOR (IY+d)    -  B | -           -            -           -           CP (IY+d)     -  C | -           #            -           -           -             -  D | -           -            -           -           -             -  E | -           EX DE,IY     -           -           -             -  F | -           -            -           -           -             -  

AFTER DDCB

DDCBddyx      6             E  --+---------------------------  0 | RLC (IX+d)    RRC (IX+d)  1 | RL (IX+d)     RR (IX+d)  2 | SLA (IX+d)    SRA (IX+d)  3 | -             SRL (IX+d)  4 | BIT 0,(IX+d)  BIT 1,(IX+d)  5 | BIT 2,(IX+d)  BIT 3,(IX+d)  6 | BIT 4,(IX+d)  BIT 5,(IX+d)  7 | BIT 6,(IX+d)  BIT 7,(IX+d)  8 | RES 0,(IX+d)  RES 1,(IX+d)  9 | RES 2,(IX+d)  RES 3,(IX+d)  A | RES 4,(IX+d)  RES 5,(IX+d)  B | RES 6,(IX+d)  RES 7,(IX+d)  C | SET 0,(IX+d)  SET 1,(IX+d)  D | SET 2,(IX+d)  SET 3,(IX+d)  E | SET 4,(IX+d)  SET 5,(IX+d)  F | SET 6,(IX+d)  SET 7,(IX+d)  

AFTER FDCB

FDCBddyx      6             E  --+---------------------------  0 | RLC (IY+d)    RRC (IY+d)  1 | RL (IY+d)     RR (IY+d)  2 | SLA (IY+d)    SRA (IY+d)  3 | -             SRL (IY+d)  4 | BIT 0,(IY+d)  BIT 1,(IY+d)  5 | BIT 2,(IY+d)  BIT 3,(IY+d)  6 | BIT 4,(IY+d)  BIT 5,(IY+d)  7 | BIT 6,(IY+d)  BIT 7,(IY+d)  8 | RES 0,(IY+d)  RES 1,(IY+d)  9 | RES 2,(IY+d)  RES 3,(IY+d)  A | RES 4,(IY+d)  RES 5,(IY+d)  B | RES 6,(IY+d)  RES 7,(IY+d)  C | SET 0,(IY+d)  SET 1,(IY+d)  D | SET 2,(IY+d)  SET 3,(IY+d)  E | SET 4,(IY+d)  SET 5,(IY+d)  F | SET 6,(IY+d)  SET 7,(IY+d)  

[ADDENDUM]

[Thunor: Added a missing instruction ED73xxxx LD (pq),SP.]

 

 

A Conversion Table from Assembly to Hex

[Thunor: Key to symbols used:

  • b - A bit e.g. 0 to 7.
  • c - A condition e.g. Z, NZ, C, NC, P, M, PO, PE.
  • d - A displacement e.g. 7B.
  • e - A single byte e.g. F7.
  • mn - A numerical constant e.g. 9E3D.
  • n - A numerical constant e.g. 3D.
  • pq - An absolute address e.g. 4C00.
  • r - A single register, an address pointed to by (HL), (IX+d) or (IY+d), and in some cases a numerical constant.
  • s - A register-pair e.g. BC, DE, HL, SP, IX, or IY.

Key end.]

The Effect of Each Instruction on the Flags

INSTRUCTIONS          FLAGS              INSTRUCTIONS          FLAGS  Opcode      Hexcode   S Z - H - P N C    Opcode      Hexcode   S Z - H - P N C  ADC A,r     table 1   @ @ - @ - @ 0 @    LD r,r      table 1   - - - - - - - -  ADC HL,s    table 2   @ @ - @ - @ 0 @    LD s,mn     table 2   - - - - - - - -  ADD A,r     table 1   @ @ - @ - @ 0 @    LD A,(pq)   3Aqqpp    - - - - - - - -  ADD HL,s    table 2   - - - @ - - 0 @    LD s,(pq)   table 2   - - - - - - - -  ADD IX,s    table 2   - - - @ - - 0 @    LD (pq),A   32qqpp    - - - - - - - -  ADD IY,s    table 2   - - - @ - - 0 @    LD (pq),s   table 2   - - - - - - - -  AND r       table 1   @ @ - 1 - @ 0 0                                           LDI         EDA0      - - - 0 - x 0 -  BIT b,r     table 1   ? @ - 1 - @ 0 0    LDD         EDA8      - - - 0 - x 0 -                                              (P/V becomes 0 if BC becomes 0)  CALL pq     CDqqpp    - - - - - - - -    LDIR        EDB0      - - - 0 - 0 0 -  CALL c,pq   table 3   - - - - - - - -    LDDR        EDB8      - - - 0 - 0 0 -  CCF         3F        - - - x - - 0 @    (the H flag becomes the previous       NEG         ED44      @ @ - @ - @ 1 @     value of the C flag)                  NOP         00        - - - - - - - -  CP r        table 1   @ @ - @ - @ 1 @  CPI         EDA1      @ x - @ - x 1 -    OR r        table 1   @ @ - 0 - @ 0 0  CPD         EDA9      @ x - @ - x 1 -    OUT (n),A   D3nn      - - - - - - - -  CPIR        EDB1      @ x - @ - x 1 -    OUT (C),r   table 1   - - - - - - - -  CPDR        EDB9      @ x - @ - x 1 -    OUTI        EDA3      ? x - ? - ? 1 -    (Z becomes 1 if BC becomes zero,       OUTD        EDAB      ? x - ? - ? 1 -     P/V becomes 1 if A = (HL-1))            (Z becomes 1 if BC becomes zero)  CPL         2F        - - - 1 - - 1 -    OTIR        EDB3      ? 1 - ? - ? 1 -                                           OTDR        EDBB      ? 1 - ? - ? 1 -  DAA         27        @ @ - @ - @ - @  DEC r       table 1   @ @ - @ - @ 1 -    POP AF      F1        x x x x x x x x  DEC s       table 2   - - - - - - - -        (Flags are determined by the  DI          F3        - - - - - - - -         byte at the top of the stack)  DJNZ e      10ee      - - - - - - - -    POP s       table 2   - - - - - - - -                                           PUSH AF     F5        - - - - - - - -  EI          FB        - - - - - - - -    PUSH s      table 2   - - - - - - - -  EX AF,AF'   08        - - - - - - - -  EX DE,HL    EB        - - - - - - - -    RES b,r     table 1   - - - - - - - -  EX (SP),HL  E3        - - - - - - - -    RET         C9        - - - - - - - -  EX (SP),IX  DDE3      - - - - - - - -    RET c       table 3   - - - - - - - -  EX (SP),IY  FDE3      - - - - - - - -    RETN        ED45      - - - - - - - -  EXX         D9        - - - - - - - -    RETI        ED4D      - - - - - - - -    HALT        76        - - - - - - - -    RLCA        07        - - - 0 - - 0 @                                           RRCA        0F        - - - 0 - - 0 @  IM 0        ED46      - - - - - - - -    RLA         17        - - - 0 - - 0 @  IM 1        ED56      - - - - - - - -    RRA         1F        - - - 0 - - 0 @  IM 2        ED5E      - - - - - - - -  INC r       table 1   @ @ - @ - @ 0 -    RLC r       table 1   @ @ - 0 - @ 0 @  INC s       table 2   - - - - - - - -    RRC r       table 1   @ @ - 0 - @ 0 @  IN A,(n)    DBnn      - - - - - - - -    RL r        table 1   @ @ - 0 - @ 0 @  IN r,(C)    table 1   @ @ - @ - @ 0 -    RR r        table 1   @ @ - 0 - @ 0 @  INI         EDA2      ? x - ? - ? 1 -  IND         EDAA      ? x - ? - ? 1 -    RRD         ED67      @ @ - 0 - @ 0 -     (Z becomes 1 if B becomes zero)       RLD         ED6F      @ @ - 0 - @ 0 -  INIR        EDB2      ? 1 - ? - ? 1 -  INDR        EDBA      ? 1 - ? - ? 1 -    RST 00      C7        - - - - - - - -  JP pq       C3qqpp    - - - - - - - -    RST 08      CF        - - - - - - - -  JP c,pq     table 3   - - - - - - - -    RST 10      D7        - - - - - - - -  JP (HL)     E9        - - - - - - - -    RST 18      DF        - - - - - - - -  JP (IX)     DDE9      - - - - - - - -    RST 20      E7        - - - - - - - -  JP (IY)     FDE9      - - - - - - - -    RST 28      EF        - - - - - - - -  JR e        18ee      - - - - - - - -    RST 30      F7        - - - - - - - -  JR c,e      table 3   - - - - - - - -    RST 38      FF        - - - - - - - -    LD (BC),A   02        - - - - - - - -    SBC A,r     table 1   @ @ - @ - @ 1 @  LD A,(BC)   0A        - - - - - - - -    SBC HL,s    table 2   @ @ - @ - @ 1 @  LD (DE),A   12        - - - - - - - -    SCF         37        - - - 0 - - 0 1  LD A,(DE)   1A        - - - - - - - -    SET b,r     table 1   - - - - - - - -                                           SLA r       table 1   @ @ - 0 - @ 0 @  LD I,A      ED47      - - - - - - - -    SRA r       table 1   @ @ - 0 - @ 0 @  LD R,A      ED4F      - - - - - - - -    SRL r       table 1   @ @ - 0 - @ 0 @  LD A,I      ED57      @ @ - 0 - x 0 -    SUB r       table 1   @ @ - @ - @ 1 @  LD A,R      ED5F      @ @ - 0 - x 0 -    (P/V is set to interrupt storage       XOR r       table 1   @ @ - 0 - @ 0 0     flag)    LD SP,HL    F9        - - - - - - - -  LD SP,IX    DDF9      - - - - - - - -  LD SP,IY    FDF9      - - - - - - - -  

Conversion Table 1

+----------------------------------------------------------------------------+  |                                  TABLE ONE                                 |  +-----------+----------------------------------------------------------------+  |     r     | B    C    D    E    H    L   (HL)  A    (IX+d)   (IY+d)   n    |  +-----------+----------------------------------------------------------------+  | ADD A,r   | 80   81   82   83   84   85   86   87   DD86dd   FD86dd   C6nn |  | ADC A,r   | 88   89   8A   8B   8C   8D   8E   8F   DD8Fdd   FD8Fdd   CEnn |  |           |                                                                |  | AND r     | A0   A1   A2   A3   A4   A5   A6   A7   DDA6dd   FDA6dd   E6nn |  |           |                                                                |  | BIT 0,r   | CB40 CB41 CB42 CB43 CB44 CB45 CB46 CB47 DDCBdd46 FDCBdd46 -    |  | BIT 1,r   | CB48 CB49 CB4A CB4B CB4C CB4D CB4E CB4F DDCBdd4E FDCBdd4E -    |  | BIT 2,r   | CB50 CB51 CB52 CB53 CB54 CB55 CB56 CB57 DDCBdd56 FDCBdd56 -    |  | BIT 3,r   | CB58 CB59 CB5A CB5B CB5C CB5D CB5E CB5F DDCBdd5E FDCBdd5E -    |  | BIT 4,r   | CB60 CB61 CB62 CB63 CB64 CB65 CB66 CB67 DDCBdd66 FDCBdd66 -    |  | BIT 5,r   | CB68 CB69 CB6A CB6B CB6C CB6D CB6E CB6F DDCBdd6E FDCBdd6E -    |  | BIT 6,r   | CB70 CB71 CB72 CB73 CB74 CB75 CB76 CB77 DDCBdd76 FDCBdd76 -    |  | BIT 7,r   | CB78 CB79 CB7A CB7B CB7C CB7D CB7E CB7F DDCBdd7E FDCBdd7E -    |  |           |                                                                |  | CP r      | B8   B9   BA   BB   BC   BD   BE   BF   DDBEdd   FDBEdd   FEnn |  | DEC r     | 05   0D   15   1D   25   2D   35   3D   DD35dd   FD35dd   -    |  |           |                                                                |  | IN r,(C)  | ED40 ED48 ED50 ED58 ED60 ED68 -    ED78 -        -        -    |  |           |                                                                |  | INC r     | 04   0C   14   1C   24   2C   34   3C   DD34dd   FD34dd   -    |  |           |                                                                |  | LD R,r    | 40   41   42   43   44   45   46   47   DD46dd   FD46dd   06nn |  | LD C,r    | 48   49   4A   4B   4C   4D   4E   4F   DD4Edd   FD4Edd   0Enn |  | LD D,r    | 50   51   52   53   54   55   56   57   DD56dd   FD56dd   16nn |  | LD E,r    | 58   59   5A   5B   5C   5D   5E   5F   DD5Edd   FD5Edd   1Enn |  | LD H,r    | 60   61   62   63   64   65   66   67   DD66dd   FD66dd   26nn |  | LD L,r    | 68   69   6A   6B   6C   6D   6E   6F   DD6Edd   FD6Edd   2Enn |  | LD (HL),r | 70   71   72   73   74   75   -    77   -        -        36nn |  | LD A,r    | 78   79   7A   7B   7C   7D   7E   7F   DD7Edd   FD7Edd   3Enn |  | LD        | DD70 DD71 DD72 DD73 DD74 DD75 -    DD77 -        -        DD36 |  | (IX+d),r  | dd   dd   dd   dd   dd   dd        dd                     ddnn |  | LD        | FD70 FD71 FD72 FD73 FD74 FD75 -    FD77 -        -        FD36 |  | (IY+d),r  | dd   dd   dd   dd   dd   dd        dd                     ddnn |  |           |                                                                |  | OR,r      | B0   B1   B2   B3   B4   B5   B6   B7   DDB6dd   FDB6dd   F6nn |  |           |                                                                |  | OUT (C),r | ED41 ED49 ED51 ED59 ED61 ED69 -    ED79 -        -        -    |  |           |                                                                |  | RES 0,r   | CB80 CB81 CB82 CB83 CB84 CB85 CB86 CB87 DDCBdd86 FDCBdd86 -    |  | RES 1,r   | CB88 CB89 CB8A CB8B CB8C CB8D CB8E CB8F DDCBdd8E FDCBdd8E -    |  | RES 2,r   | CB90 CB91 CB92 CB93 CB94 CB95 CB96 CB97 DDCBdd96 FDCBdd96 -    |  | RES 3,r   | CB98 CB99 CB9A CB9B CB9C CB9D CB9E CB9F DDCBdd9E FDCBdd9E -    |  | RES 4,r   | CBA0 CBA1 CBA2 CBA3 CBA4 CBA5 CBA6 CBA7 DDCBddA6 FDCBddA6 -    |  | RES 5,r   | CBA8 CBA9 CBAA CBAB CBAC CBAD CBAE CBAF DDCBddAE FDCBddAE -    |  | RES 6,r   | CBB0 CBB1 CBB2 CBB3 CBB4 CBB5 CBB6 CBB7 DDCBddB6 FDCBddB6 -    |  | RES 7,r   | CBB8 CBB9 CBBA CBBB CBBC CBBD CBBE CBBF DDCBddBE FDCBddBE -    |  |           |                                                                |  | RLC r     | CB00 CB01 CB02 CB03 CB04 CB05 CB06 CB07 DDCBdd06 FDCBdd06 -    |  | RRC r     | CB08 CB09 CB0A CB0B CB0C CB0D CB0E CB0F DDCBdd0E FDCBdd0E -    |  | RL r      | CB10 CB11 CB12 CB13 CB14 CB15 CB16 CB17 DDCBdd16 FDCBdd16 -    |  | RR r      | CB18 CB19 CB1A CB1B CB1C CB1D CB1E CB1F DDCBdd1E FDCBdd1E -    |  |           |                                                                |  | SET 0,r   | CBC0 CBC1 CBC2 CBC3 CBC4 CBC5 CBC6 CBC7 DDCBddC6 FDCBddC6 -    |  | SET 1,r   | CBC8 CBC9 CBCA CBCB CBCC CBCD CBCE CBCF DDCBddCE FDCBddCE -    |  | SET 2,r   | CBD0 CBD1 CBD2 CBD3 CBD4 CBD5 CBD6 CBD7 DDCBddD6 FDCBddD6 -    |  | SET 3,r   | CBD8 CBD9 CBDA CBDB CBDC CBDD CBDE CBDF DDCBddDE FDCBddDE -    |  | SET 4,r   | CBE0 CBE1 CBE2 CBE3 CBE4 CBE5 CBE6 CBE7 DDCBddE6 FDCBddE6 -    |  | SET 5,r   | CBE8 CBE9 CBEA CBEB CBEC CBED CBEE CBEF DDCBddEE FDCBddEE -    |  | SET 6,r   | CBF0 CBF1 CBF2 CBF3 CBF4 CBF5 CBF6 CBF7 DDCBddF6 FDCBddF6 -    |  | SET 7,r   | CBF8 CBF9 CBFA CBFB CBFC CBFD CBFE CBFF DDCBddFE FDCBddFE -    |  |           |                                                                |  | SUB A,r   | 90   91   92   93   94   95   96   97   DD96dd   FD96dd   D6nn |  | SBC A,r   | 98   99   9A   9B   9C   9D   9E   9F   DD9Edd   FD9Edd   DEnn |  |           |                                                                |  | SLA r     | CB20 CB21 CB22 CB23 CB24 CB25 CB26 CB27 DDCBdd26 FDCBdd26 -    |  | SRA r     | CB28 CB29 CB2A CB2B CB2C CB2D CB2E CB2F DDCBdd2E FDCBdd2E -    |  | SRL r     | CB38 CB39 CB3A CB3B CB3C CB3D CB3E CB3F DDCBdd3E FDCBdd3E -    |  |           |                                                                |  | XOR r     | A8   A9   AA   AB   AC   AD   AE   AF   DDAEdd   FDAEdd   EEnn |  +-----------+----------------------------------------------------------------+  

Conversion Table 2

+------------------------------------------------------------------------+  |                                TABLE TWO                               |  +-----------+------------------------------------------------------------+  |     s     | BC        DE        HL        SP        IX        IY       |  +-----------+------------------------------------------------------------+  | ADC HL,s  | ED4A      ED5A      ED6A      ED7A      -         -        |  | ADD HL,s  | 09        19        29        39        -         -        |  | ADD IX,s  | DD09      DD19      -         DD39      DD29      -        |  | ADD IY,s  | FD09      FD19      -         FD39      -         FD29     |  |           |                                                            |  | DEC s     | 0B        1B        2B        3B        DD2B      FD2B     |  |           |                                                            |  | INC s     | 03        13        23        33        DD23      FD23     |  |           |                                                            |  | LD s,mn   | 01nnmm    11nnmm    21nnmm    31nnmm    DD21nnmm  FD21nnmm |  | LD s,(pq) | ED4Bqqpp  ED5Bqqpp  2Aqqpp    ED7Bqqpp  DD2Aqqpp  FD2Aqqpp |  | LD (pq),s | ED43qqpp  ED53qqpp  22qqpp    ED73qqpp  DD22qqpp  FD22qqpp |  |           |                                                            |  | POP s     | C1        D1        E1        -         DDE1      FDE1     |  |           |                                                            |  | PUSH s    | C5        D5        E5        -         DDE5      FDE5     |  |           |                                                            |  | SBC HL,s  | ED42      ED52      ED62      ED72      -         -        |  +-----------+------------------------------------------------------------+  

Conversion Table 3

+----------------------------------------------------------------------------+  |                                 TABLE THREE                                |  +-----------+----------------------------------------------------------------+  |     c     | NZ      Z       NC      C       PO      PE      P       M      |  +-----------+----------------------------------------------------------------+  | CALL c,pq | C4qqpp  CCqqpp  D4qqpp  DCqqpp  E4qqpp  ECqqpp  F4qqpp  FCqqpp |  | JP c,pq   | C2qqpp  CAqqpp  D2qqpp  DAqqpp  E2qqpp  EAqqpp  F2qqpp  FAqqpp |  | JR c,e    | 20ee    28ee    30ee    38ee    -       -       -       -      |  | RET c     | C0      C8      D0      D8      E0      E8      F0      F8     |  +-----------+----------------------------------------------------------------+  

 

The ZX Character Set

[Thunor: Key to symbols used:

  • Characters within square brackets are inverse e.g. [=].
  • Unused characters are presented by two tildes e.g. ~~.

If there are missing characters then it means that your browser isn't displaying the graphics (there are 21 images).

Key end.]

OLD ROM Characters

+---------------------------------------------------------------------------+  |   |   0    |   1    |   2    |   3    |   4    |   5    |   6    |   7    |  |   +-----------------------------------------------------------------------+  | 0 |space   |nullstr |      |      |      |      |      |      |  | 1 |(       |)       |-       |+       |*       |/       |=       |>       |  | 2 |4       |5       |6       |7       |8       |9       |A       |B       |  | 3 |K       |L       |M       |N       |O       |P       |Q       |R       |  | 4 |~~      |~~      |~~      |~~      |~~      |~~      |~~      |~~      |  | 5 |~~      |~~      |~~      |~~      |~~      |~~      |~~      |~~      |  | 6 |~~      |~~      |~~      |~~      |~~      |~~      |~~      |~~      |  | 7 |up      |down    |left    |right   |HOME    |EDIT    |NEWLINE |RUBOUT  |  | 8 |      |["]     |      |      |      |      |      |      |  | 9 |[(]     |[)]     |[-]     |[+]     |[*]     |[/]     |[=]     |[>]     |  | A |[4]     |[5]     |[6]     |[7]     |[8]     |[9]     |[A]     |[B]     |  | B |[K]     |[L]     |[M]     |[N]     |[O]     |[P]     |[Q]     |[R]     |  | C |~~      |~~      |~~      |~~      |~~      |~~      |~~      |~~      |  | D |~~      |~~      |~~      |~~      |"       |THEN    |TO      |;       |  | E |AND     |OR      |**      |=       |>       |<       |LIST    |RETURN  |  | F |LET     |~~      |~~      |NEXT    |PRINT   |~~      |NEW     |RUN     |  +---+-----------------------------------------------------------------------+  |   |   8    |   9    |   A    |   B    |   C    |   D    |   E    |   F    |  |   +-----------------------------------------------------------------------+  | 0 |      |      |      |      |£       |$       |:       |?       |  | 1 |<       |;       |,       |.       |0       |1       |2       |3       |  | 2 |C       |D       |E       |F       |G       |H       |I       |J       |  | 3 |S       |T       |U       |V       |W       |X       |Y       |Z       |  | 4 |~~      |~~      |~~      |~~      |~~      |~~      |~~      |~~      |  | 5 |~~      |~~      |~~      |~~      |~~      |~~      |~~      |~~      |  | 6 |~~      |~~      |~~      |~~      |~~      |~~      |~~      |~~      |  | 7 |~~      |~~      |~~      |~~      |~~      |~~      |~~      |~~      |  | 8 |      |      |      |      |[£]     |[$]     |[:]     |[?]     |  | 9 |[<]     |[;]     |[,]     |[.]     |[0]     |[1]     |[2]     |[3]     |  | A |[C]     |[D]     |[E]     |[F]     |[G]     |[H]     |[I]     |[J]     |  | B |[S]     |[T]     |[U]     |[V]     |[W]     |[X]     |[Y]     |[Z]     |  | C |~~      |~~      |~~      |~~      |~~      |~~      |~~      |~~      |  | D |,       |)       |(       |NOT     |-       |+       |*       |/       |  | E |CLS     |DIM     |SAVE    |FOR     |GO TO   |POKE    |INPUT   |RANDOMI.|  | F |STOP    |CONTINUE|IF      |GO SUB  |LOAD    |CLEAR   |REM     |~~      |  +---+-----------------------------------------------------------------------+  

NEW ROM Characters

+---------------------------------------------------------------------------+  |   |   0    |   1    |   2    |   3    |   4    |   5    |   6    |   7    |  |   +-----------------------------------------------------------------------+  | 0 |space   |      |      |      |      |      |      |      |  | 1 |(       |)       |>       |<       |=       |+       |-       |*       |  | 2 |4       |5       |6       |7       |8       |9       |A       |B       |  | 3 |K       |L       |M       |N       |O       |P       |Q       |R       |  | 4 |RND     |PI      |INKEY$  |~~      |~~      |~~      |~~      |~~      |  | 5 |~~      |~~      |~~      |~~      |~~      |~~      |~~      |~~      |  | 6 |~~      |~~      |~~      |~~      |~~      |~~      |~~      |~~      |  | 7 |up      |down    |left    |right   |GRAPHICS|EDIT    |NEWLINE |RUBOUT  |  | 8 |      |      |      |      |      |      |      |      |  | 9 |[(]     |[)]     |[>]     |[<]     |[=]     |[+]     |[-]     |[*]     |  | A |[4]     |[5]     |[6]     |[7]     |[8]     |[9]     |[A]     |[B]     |  | B |[K]     |[L]     |[M]     |[N]     |[O]     |[P]     |[Q]     |[R]     |  | C |""      |AT      |TAB     |~~      |CODE    |VAL     |LEN     |SIN     |  | D |SQR     |SGN     |ABS     |PEEK    |USR     |STR$    |CHR$    |NOT     |  | E |STEP    |LPRINT  |LLIST   |STOP    |SLOW    |FAST    |NEW     |SCROLL  |  | F |LIST    |LET     |PAUSE   |NEXT    |POKE    |PRINT   |PLOT    |RUN     |  +---+-----------------------------------------------------------------------+  |   |   8    |   9    |   A    |   B    |   C    |   D    |   E    |   F    |  |   +-----------------------------------------------------------------------+  | 0 |      |      |      |"       |£       |$       |:       |?       |  | 1 |/       |;       |,       |.       |0       |1       |2       |3       |  | 2 |C       |D       |E       |F       |G       |H       |I       |J       |  | 3 |S       |T       |U       |V       |W       |X       |Y       |Z       |  | 4 |~~      |~~      |~~      |~~      |~~      |~~      |~~      |~~      |  | 5 |~~      |~~      |~~      |~~      |~~      |~~      |~~      |~~      |  | 6 |~~      |~~      |~~      |~~      |~~      |~~      |~~      |~~      |  | 7 |[K]/[L] |FUNCTION|~~      |~~      |~~      |~~      |number  |cursor  |  | 8 |      |      |      |["]     |[£]     |[$]     |[:]     |[?]     |  | 9 |[/]     |[;]     |[,]     |[.]     |[0]     |[1]     |[2]     |[3]     |  | A |[C]     |[D]     |[E]     |[F]     |[G]     |[H]     |[I]     |[J]     |  | B |[S]     |[T]     |[U]     |[V]     |[W]     |[X]     |[Y]     |[Z]     |  | C |COS     |TAN     |ASN     |ACS     |ATN     |LN      |EXP     |INT     |  | D |**      |OR      |AND     |<=      |>=      |<>      |THEN    |TO      |  | E |CONT    |DIM     |REM     |FOR     |GOTO    |GOSUB   |INPUT   |LOAD    |  | F |SAVE    |RAND    |IF      |CLS     |UNPLOT  |CLEAR   |RETURN  |COPY    |  +---+-----------------------------------------------------------------------+  

A Listing of the Program Draughts

DRAUGHTS - NEW ROM

Added by myself (Thunor) from assembling the many machine code routines distributed across chapters 7, 11, 13 and 15. If you haven't read these chapters then I should point out that draughts is not a complete program, although you can move your pieces and the computer will respond up until a certain point.

Program Organisation  This should be written with HEXLD3D (chapter11-hexld3d.p)  which has its machine code located between 4A82 and 4B77  (RAMTOP should be set to 4A00).    Program Listing  4C09: E1        SPRINT    POP HL  4C0A: 7E                  LD A,(HL)  4C0B: 23                  INC HL  4C0C: E5                  PUSH HL  4C0D: FEFF                CP FF  4C0F: C8                  RET Z  4C10: CD0808              CALL PRINT  4C13: 18F4                JR SPRINT  4C15: CD094C              CALL SPRINT  4C18: 001D1E1F202122232476          DEFM " 12345678"  4C22: 1D00BC00BC00BC00BC1D76        DEFM "1 W W W W1"  4C2D: 1EBC00BC00BC00BC001E76        DEFM "2W W W W 2"  4C38: 1F00BC00BC00BC00BC1F76        DEFM "3 W W W W3"  4C43: 2080008000800080002076        DEFM "4        4"  4C4E: 2100800080008000802176        DEFM "5        5"  4C59: 22A700A700A700A7002276        DEFM "6B B B B 6"  4C64: 2300A700A700A700A72376        DEFM "7 B B B B7"  4C6F: 24A700A700A700A7002476        DEFM "8B B B B 8"  4C7A: 001D1E1F202122232476          DEFM " 12345678"  4C84: 76                            DEFB 76  4C85: 76                            DEFB 76  4C86: 76                            DEFB 76  4C87: 0000000000000000000000000000  DEFM "fourteen-spaces"  4C95: FF                            DEFB FF  4C96: C9                  RET  142 bytes.    4C97: FAFB0605  TABLE     DEFB FA FB 06 05  4 bytes.    4C9B: 2E31312A  IMOVE     DEFM "ILLE"  4C9F: 2C263100            DEFM "GAL "  4CA3: 32343B2A            DEFM "MOVE"  4CA7: E1        ERROR     POP HL  4CA8: 7E                  LD A,(HL)  4CA9: 2A0C40              LD HL,(D_FILE)  4CAC: 117000              LD DE,0700  4CAF: 19                  ADD HL,DE  4CB0: EB                  EX DE,HL  4CB1: 219B4C              LD HL,IMOVE  4CB4: 010C00              LD BC,000C  4CB7: EDB0                LDIR  4CB9: 13                  INC DE  4CBA: 12                  LD (DE),A  4CBB: C9                  RET  33 bytes.    4CBC: 213C40    GAMEOVER  LD HL,WKBOARD  4CBF: 062A                LD B,2A  4CC1: 7E        POSSIBLY  LD A,(HL)  4CC2: F680                OR 80  4CC4: B9                  CP C  4CC5: C8                  RET Z  4CC6: 23                  INC HL  4CC7: 10F8                DJNZ POSSIBLY  4CC9: 219740    STOPPROG  LD HL,STOPLINE  4CCC: 222940              LD (NXTLIN),HL  19 bytes.    4CCF: 2A0C40    INVERT    LD HL,(D_FILE)  4CD2: 066C                LD B,6C  4CD4: 23        COVER     INC HL  4CD5: 7E                  LD A,(HL)  4CD6: FE25                CP 25  4CD8: 3006                JR NC,NOINV  4CDA: A7                  AND A  4CDB: 2803                JR Z,NOINV  4CDD: F680                OR 80  4CDF: 77                  LD (HL),A  4CE0: 10F2      NOINV     DJNZ COVER  4CE2: E1                  POP HL  4CE3: C9                  RET  21 bytes.    4CE4: 2A0C40    BOARDCOPY LD HL,(D_FILE)  4CE7: 110D00              LD DE,000D  4CEA: 19                  ADD HL,DE  4CEB: 113C40              LD DE,WKBOARD  4CEE: 062A                LD B,2A  4CF0: EDA0      NSCOPY    LDI  4CF2: 23                  INC HL  4CF3: 10FB                DJNZ,NSCOPY  17 bytes.    4CF5: 217D40    NEXTLINE  LD HL,FIRSTLINE  4CF8: 222940              LD (NXTLIN),HL  4CFB: 2A0C40    CLWIND    LD HL,(D_FILE)  4CFE: 117000              LD DE,0070  4D01: 19                  ADD HL,DE  4D02: 060E                LD B,0E  4D04: 3600      WIPEOUT   LD (HL),00  4D06: 23                  INC HL  4D07: 10FB                DJNZ WIPEOUT  20 bytes.    4D09: 2A1040    MOVE      LD HL,(VARS)  4D0C: 23                  INC HL  4D0D: 7E                  LD A,(HL)  4D0E: 3D                  DEC A  4D0F: 3D                  DEC A  4D10: 3D                  DEC A  4D11: 2001                JR NZ,NOTZERO  4D13: 2F                  CPL  4D14: 5F        NOTZERO   LD E,A  12 bytes.    4D15: 23                  INC HL  4D16: 23                  INC HL  4D17: 7E                  LD A,(HL)  4D18: 47                  LD B,A  4D19: 87                  ADD A,A  4D1A: 4F                  LD C,A  4D1B: 87                  ADD A,A  4D1C: 87                  ADD A,A  4D1D: 23                  INC HL  4D1E: E5                  PUSH HL  4D1F: 80                  ADD A,B  4D20: 81                  ADD A,C  4D21: 86                  ADD A,(HL)  4D22: 1F                  RRA  4D23: 3805                JR C,NOERROR1  16 bytes.    4D25: E1        ERROR1    POP HL  4D26: CDA74C              CALL ERROR  4D29: 1D                  DEFM "1"  5 bytes.    4D2A: C60E      NOERROR1  ADD A,0E  4D2C: 6F                  LD L,A  4D2D: 2640                LD H,WKBOARD-high  4D2F: 4E                  LD C,(HL)  4D30: 0680                LD B,80  4D32: 70                  LD (HL),B  4D33: E3        LOOP      EX (SP),HL  4D34: 23                  INC HL  4D35: 227B40              LD (POINTER),HL  4D38: 7E                  LD A,(HL)  4D39: C671                ADD A,71  4D3B: 6F                  LD L,A  4D3C: 264C                LD H,TABLE-high  4D3E: 56                  LD D,(HL)  4D3F: E1                  POP HL  4D40: 78                  LD A,B  4D41: A2                  AND D  4D42: 2F                  CPL  4D43: A1                  AND C  4D44: FE27                CP 27  4D46: 20DE                JR NZ,ERROR1  30 bytes.    4D48: 7D                  LD A,L  4D49: 82                  ADD A,D  4D4A: 6F                  LD L,A  4D4B: 7E                  LD A,(HL)  4D4C: B8                  CP B  4D4D: 2008                JR NZ,NEXT  4D4F: 7B                  LD A,E  4D50: 3C                  INC A  4D51: 2815                JR Z,CONTINUE  4D53: CDA74C              CALL ERROR  4D56: 1E                  DEFM "2"  4D57: B0        NEXT      OR B  4D58: FEBC                CP BC  4D5A: 2804                JR Z,NOERROR3  4D5C: CDA74C    ERROR3    CALL ERROR  4D5F: 1F                  DEFM "3"  4D60: 70        NOERROR3  LD (HL),B  4D61: 7D                  LD A,L   4D62: 82                  ADD A,D  4D63: 6F                  LD L,A  4D64: 7E        CONTENT   LD A,(HL)  4D65: B8                  CP B  4D66: 20F4                JR NZ,ERROR3  32 bytes.    4D68: 7D        CONTINUE  LD A,L   4D69: FE40                CP 40  4D6B: 300C                JR NC,NOKING  4D6D: 7B                  LD A,E  4D6E: 3C                  INC A  4D6F: FE02                CP 02  4D71: 3804                JR C,NOERROR4  4D73: CDA74C              CALL ERROR  4D76: 20                  DEFM "4"  4D77: 0E27      NOERROR4  LD C,27  4D79: 71        NOKING    LD (HL),C  4D7A: E5                  PUSH HL  19 bytes.    4D7B: 2A7B40              LD HL,(POINTER)  4D7E: 1D                  DEC E  4D7F: 7B                  LD A,E  4D80: E3                  EX (SP),HL  4D81: 17                  RLA  4D82: 30AE                JR NC,LOOP  4D84: E1                  POP HL  4D85: 0EBC                LD C,BC  4D87: CDBC4C              CALL GAMEOVER  15 bytes.    4D8A: ED737B40  BOARDSCAN LD (LBASE),SP  4D8E: 010000              LD BC,0000  4D91: C5                  PUSH BC  4D92: 213C40              LD HL,WKBOARD  4D95: 7E        NXTCHK    LD A,(HL)  4D96: F680                OR 80  4D98: FEBC                CP BC  4D9A: 227740              LD (SQCHK),HL  4D9D: CA434E              JP Z,EVALUATE  4DA0: 2A7740    KPCHKNG   LD HL,(SQCHK)  4DA3: 2C                  INC L  4DA4: 7D                  LD A,L  4DA5: FE66                CP 66  4DA7: 20EC                JR NZ,NXTCHK  31 bytes.    4DA9: 3A3440    CHOOSE    LD A,(FRAMES)low  4DAC: 90        REPEAT    SUB B  4DAD: 30FD                JR NC,REPEAT  4DAF: 80                  ADD A,B  4DB0: C1                  POP BC  4DB1: 41                  LD B,C  4DB2: 2808                JR Z,FIRSTOFF  4DB4: 33        NSQOFF    INC SP  4DB5: 33        NEXTOFF   INC SP  4DB6: 10FD                DJNZ NEXTOFF  4DB8: 41                  LD B,C  4DB9: 3D                  DEC A  4DBA: 20F8                JR NZ,NSQOFF  19 bytes.    4DBC: E1        FIRSTOFF  POP HL  4DBD: 2640                LD H,WKBOARD-high  4DBF: 41                  LD B,C  4DC0: 3B        NEXTSTEP  DEC SP  4DC1: D1                  POP DE  4DC2: 4E                  LD C,(HL)  4DC3: 3680                LD (HL),80  4DC5: 7D                  LD A,L  4DC6: 83                  ADD A,E  4DC7: 6F                  LD L,A  4DC8: 7E                  LD A,(HL)  4DC9: FE80                CP 80  4DCB: 2805                JR Z,SQUARE  4DCD: 3680                LD (HL),80  4DCF: 7D                  LD A,L  4DD0: 83                  ADD A,E  4DD1: 6F                  LD L,A  4DD2: 71        SQUARE    LD (HL),C  4DD3: 10EB                DJNZ NEXTSTEP  25 bytes.    4DD5: ED7B7B40            LD SP,(LBASE)  4DD9: 0EA7                LD C,A7  4DDB: CDBC4C              CALL GAMEOVER  4DDE: 2A0C40    BDPRINT   LD HL,(D_FILE)  4DE1: 110D00              LD DE,000D  4DE4: 19                  ADD HL,DE  4DE5: EB                  EX DE,HL  4DE6: 213C40              LD HL,WKBOARD  4DE9: 062A                LD B,2A  4DEB: EDA0      LDI       LDI  4DED: 13                  INC DE  4DEE: 10FB                DJNZ LDI  4DF0: C9                  RET  28 bytes.    4DF1: C5        SQUAREVAL PUSH BC  4DF2: D5                  PUSH DE  4DF3: E5                  PUSH HL  4DF4: 0600                LD B,00  4DF6: 11974C    STARTOFF  LD DE,TABLE  4DF9: 1A        NOWT      LD A,(DE)  4DFA: 4F                  LD C,A  4DFB: D62E                SUB 2E  4DFD: 282A                JR Z,EXIT  4DFF: 1C                  INC E  4E00: E1                  POP HL  4E01: E5                  PUSH HL  4E02: 2640                LD H,40  4E04: 7D                  LD A,L           4E05: 81                  ADD A,C  4E06: CB40                BIT 0,B  4E08: 2001                JR NZ,LA  4E0A: 81                  ADD A,C  4E0B: 6F        LA        LD L,A  4E0C: 3E7F                LD A,7F  4E0E: B1                  OR C  4E0F: A6                  AND (HL)  4E10: FE27                CP 27  4E12: 20E5                JR NZ,NOWT  4E14: 7D                  LD A,L  4E15: 91                  SUB C  4E16: 6F                  LD L,A  4E17: 7E                  LD A,(HL)  4E18: 37                  SCF  4E19: 17                  RLA  4E1A: CB40                BIT 0,B  4E1C: 2006                JR NZ,LB  4E1E: FE79                CP 79  4E20: 20D7                JR NZ,NOWT  4E22: 7E                  LD A,(HL)  4E23: 17                  RLA  4E24: 3F        LB        CCF  4E25: 3E81                LD A,81  4E27: 17                  RLA  4E28: 17                  RLA  4E29: CB40      EXIT      BIT 0,B  4E2B: 2006                JR NZ,LC  4E2D: 04                  INC B  4E2E: 67                  LD H,A  4E2F: E3                  EX (SP),HL  4E30: E5                  PUSH HL  4E31: 18C3                JR STARTOFF  4E33: 57        LC        LD D,A  4E34: 7D                  LD A,L  4E35: 91                  SUB C  4E36: 6F                  LD L,A  4E37: 7E                  LD A,(HL)  4E38: FE80                CP 80  4E3A: 28BD                JR Z,NOWT  4E3C: 7A                  LA A,D  4E3D: E1                  POP HL  4E3E: D1                  POP DE  4E3F: 92                  SUB D  4E40: D1                  POP DE  4E41: C1                  POP BC  4E42: C9                  RET  82 bytes.    4E43: CDF14D    EVALUATE  CALL SQUAREVAL   4E46: C680                ADD 80  4E48: 322140              LD (INITIAL),A  4E4B: 11974C              LD DE,TABLE  4E4E: 4D                  LD C,L  4E4F: 69        NXTMRND   LD L,C  4E50: 2640                LD H,40  4E52: 1A        NXTDIR    LD A,(DE)  4E53: 1C                  INC E  4E54: CB7E                BIT 7,(HL)  4E56: 2804                JR Z,ANYDIR  4E58: CB7F                BIT 7,A  4E5A: 20F6                JR NZ,NXTDIR  4E5C: FE2E      ANYDIR    CP 2E  4E5E: CAA04D              JP Z,KPCHKNG  4E61: C5                  PUSH BC  4E62: 47                  LD B,A  4E63: B1                  ADD A,C  4E64: 6F                  LD L,A  4E65: 7E                  LD A,(HL)  4E66: 60                  LD H,B  4E67: C1                  POP BC  4E68: FE80                CP 80  4E6A: 2033      TEST      JR NZ,WHAT  4E6C: ED537940            LD (SCANSQR),DE  4E70: CDF14D              CALL SQUAREVAL  48 bytes.    4E73: 57        NEWPRI    LD D,A  4E74: 3A2140              LD A,(INITIAL)  4E77: 92                  SUB D  4E78: 57                  LD D,A  4E79: 1E01                LD E,01  4E7B: 69                  LD L,C  9 bytes.    4E7C: E3                  EX (SP),HL  4E7D: A7                  AND A  4E7E: ED52                SBC HL,DE  4E80: 280D                JR Z,EQUAL  4E82: 19                  ADD HL,DE  4E83: E3                  EX (SP),HL  4E84: 3013                JR NC,FORGETIT  4E86: ED7B7B40            LD SP,(LBASE)  4E8A: 0600                LD B,00  4E8C: D5                  PUSH DE  4E8D: 1802                JR NEWITEM  4E8F: 19        EQUAL     ADD HL,DE  4E90: E3                  EX (SP),HL  4E91: 04        NEWITEM   INC B  4E92: E5                  PUSH HL  23 bytes.    4E93: 33                  INC SP  4E94: 33                  INC SP  4E95: E3                  EX (SP),HL  4E96: 3B                  DEC SP  4E97: 3B                  DEC SP  4E98: E3                  EX (SP),HL  4E99: ED5B7940  FORGETIT  LD DE,(SCANSQR)  4E9D: 18B0                JR NXTMRND  12 bytes.    4E9F: ED537940  WHAT      LD (SCANSQR),DE  4EA3: 57                  LD D,A  4EA4: E67F                AND 7F  4EA6: FE27                CP 27  4EA8: 2806                JR Z,FOUND  4EAA: ED5B7940            LD DE,(SCANSQR)  4EAE: 189F                JR NXTMRND  4EB0: 3E81      FOUND     LD A,81  4EB2: CB12                RL D  4EB4: 3F                  CCF  4EB5: 17                  RLA  4EB6: 17                  RLA  4EB7: 57                  LD D,A  4EB8: 5C                  LD E,H  4EB9: 7D                  LD A,L  4EBA: 84                  ADD A,H  4EBB: 6F                  LD L,A  4EBC: 2640                LD H,WKBOARD-low  4EBE: 7E                  LD A,(HL)  4EBF: 63                  LD H,E  4EC0: FE80                CP 80  4EC2: 2807                JR Z,JUMP  4EC4: ED537940            LD DE,(SCANSQR)  4EC8: C34F4E              JP NXTMRND  4ECB: CDF14D    JUMP      CALL SQUAREVAL  4ECE: 92                  SUB D  4ECF: 18A2                JR NEWPRI  50 bytes.    Total 712 bytes.    The BASIC Part  1 INPUT A$  2 RAND USR 19684  3 STOP  4 RAND USR 19477  5 RUN    The above lines are in addition to the BASIC lines of  HEXLD3D which you still need to restore the machine  code to its correct destination.    Operating Instructions  Type RUN 4 to run draughts.    You play as black at the bottom of the board.    You can move one of your pieces by typing the row and  column of its current location followed by a letter  signifying a direction. The letters are:    A   B   \ /    X   / \  D   C    So "61B" will move your top-leftmost piece north-east.  

Download available for 16K ZX81 -> chapter15-draughts3.p

 

 

 

A Farewell Program

(NEW ROM Users Only!)

This program looks particularly effective when run in the SLOW mode. I'm not telling you what it does - feed it in and find out....

BASIC:    1 REM one hundred and sixty one characters            2 RAND USR 16514    MACHINE CODE: (To be written to address 4082 - decimal 16514):      21B940    LD HL,40B9        01FF0001    DEFB 01 FF 00 01  7E        LD A,(HL)         0100FFFF    DEFB 01 00 FF FF  23        INC HL            FE050C4E    DEFB FE 05 0C 4E  1F        RRA               7C54004E    DEFB 7C 54 00 4E  F5        PUSH AF           7C55B4AE    DEFB 7C 55 B4 AE  D7        RST 10            B2B2B1B1    DEFB B2 B2 B1 B1  F1        POP AF            B1B1B1B1    DEFB B1 B1 B1 B1  30F8      JR NC,F8          B1B1B0B4    DEFB B1 B1 B0 B4  0680      LD B,80           B5B5B3B3    DEFB B5 B5 B3 B3  3D        DEC A             B3B3B5B3    DEFB B3 B3 B5 B3  20FD      JR NZ,FD          B3B5B3B3    DEFB B3 B5 B3 B3  10FB      DJNZ FB           B5B3B3B3    DEFB B5 B3 B3 B3  01240A    LD BC,0A24        B3B0B0B0    DEFB B3 B0 B0 B0  C5        PUSH BC           B0B1B1B1    DEFB B0 B1 B1 B1  E5        PUSH HL           B0B1B0B1    DEFB B0 B1 B0 B1  CDB60B    CALL 0BB6         B1AEB3B3    DEFB B1 AE B3 B3  C1        POP BC            B5AFB0B0    DEFB B5 AF B0 B0  0A        LD A,(BC)         B1AEB1B0    DEFB B1 AE B1 B0  6F        LD L,A            B1B3B3B3    DEFB B1 B3 B3 B3  2640      LD H,40           B3B0B0B1    DEFB B3 B0 B0 B1  5E        LD E,(HL)         B1B3B3B3    DEFB B1 B3 B3 B3  2C        INC L             B1B1B0B0    DEFB B1 B1 B0 B0  56        LD D,(HL)         AEB2B1B2    DEFB AE B2 B1 B2  E1        POP HL            B1B2B1B2    DEFB B1 B2 B1 B2  C8        RET Z             B2B2B4B5    DEFB B2 B2 B4 B5  19        ADD HL,DE         B5B5B4B5    DEFB B5 B5 B4 B5  C5        PUSH BC           B5B4B5B5    DEFB B5 B4 B5 B5  4D        LD C,L            B4B5B5B4    DEFB B4 B5 B5 B4  44        LD B,H            B5B5AEB7    DEFB B5 B5 AE B7  E1        POP HL            FF          DEFB FF  23        INC HL  18E9      JR E9  

[Download available for 16K ZX81 -> farewell.p]